docs: 프로젝트 문서화 및 개발 가이드 추가
- 데이터베이스 스키마 및 변경 로그 문서화 - 신규 페이지 개발 가이드 작성 - 모듈 아키텍처 설계 문서 추가 - 성능 최적화 전략 문서화 - 리팩토링 계획 및 진행 상황 정리 Documentation: - DATABASE_SCHEMA.md: 전체 DB 스키마 구조 - DB_CHANGE_LOG.md: 마이그레이션 변경 이력 - DEVELOPMENT_GUIDE.md: 신규 기능 개발 표준 - MODULE_ARCHITECTURE.md: 프론트엔드 모듈 구조 - PERFORMANCE_OPTIMIZATION.md: 성능 최적화 가이드 - REFACTORING_PLAN.md: 리팩토링 진행 상황 Test Files: - app.html, app.js: SPA 테스트 파일 - test_api.html: API 테스트 페이지
This commit is contained in:
511
DEVELOPMENT_GUIDE.md
Normal file
511
DEVELOPMENT_GUIDE.md
Normal file
@@ -0,0 +1,511 @@
|
||||
# M-Project 개발 가이드
|
||||
|
||||
## 개요
|
||||
M-Project의 신규 페이지 및 기능 개발을 위한 표준 가이드입니다.
|
||||
|
||||
---
|
||||
|
||||
## 📋 기본 규칙
|
||||
|
||||
### 1. 파일 구조 규칙
|
||||
```
|
||||
frontend/
|
||||
├── [page-name].html # 메인 HTML 파일
|
||||
├── static/
|
||||
│ ├── js/
|
||||
│ │ ├── modules/
|
||||
│ │ │ └── [page-name]/ # 페이지별 모듈
|
||||
│ │ │ ├── [page-name].js
|
||||
│ │ │ ├── components/ # 페이지 전용 컴포넌트
|
||||
│ │ │ └── utils/ # 페이지 전용 유틸리티
|
||||
│ │ ├── components/ # 공통 컴포넌트
|
||||
│ │ ├── core/ # 핵심 시스템
|
||||
│ │ └── utils/ # 공통 유틸리티
|
||||
│ └── css/
|
||||
│ └── [page-name].css # 페이지별 스타일 (필요시)
|
||||
```
|
||||
|
||||
### 2. 네이밍 규칙
|
||||
- **파일명**: kebab-case (`user-management.html`)
|
||||
- **클래스명**: PascalCase (`UserManagementModule`)
|
||||
- **함수명**: camelCase (`loadUserData`)
|
||||
- **변수명**: camelCase (`currentUser`)
|
||||
- **상수명**: UPPER_SNAKE_CASE (`API_BASE_URL`)
|
||||
- **CSS 클래스**: kebab-case (`user-list-item`)
|
||||
|
||||
### 3. 권한 체크 규칙
|
||||
- 모든 페이지는 권한 체크를 구현해야 함
|
||||
- 페이지별 권한명은 `[category]_[action]` 형식 사용
|
||||
- 예: `users_manage`, `reports_view`, `projects_create`
|
||||
|
||||
---
|
||||
|
||||
## 🚀 신규 페이지 개발 단계
|
||||
|
||||
### 1단계: 권한 정의
|
||||
```javascript
|
||||
// backend/routers/page_permissions.py의 DEFAULT_PAGES에 추가
|
||||
'new_feature': {'title': '새 기능', 'default_access': false}
|
||||
```
|
||||
|
||||
### 2단계: HTML 템플릿 생성
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>새 기능 - 작업보고서</title>
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- 페이지별 스타일 (필요시) -->
|
||||
<link rel="stylesheet" href="/static/css/new-feature.css">
|
||||
</head>
|
||||
<body class="bg-gray-50">
|
||||
<!-- 공통 헤더는 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main id="main-content" class="container mx-auto px-4 py-8">
|
||||
<!-- 페이지 콘텐츠 -->
|
||||
</main>
|
||||
|
||||
<!-- 필수 스크립트 -->
|
||||
<script src="/static/js/core/permissions.js?v=20251025"></script>
|
||||
<script src="/static/js/components/common-header.js?v=20251025"></script>
|
||||
<script src="/static/js/core/page-manager.js?v=20251025"></script>
|
||||
|
||||
<!-- 페이지별 모듈 -->
|
||||
<script src="/static/js/modules/new-feature/new-feature.js?v=20251025"></script>
|
||||
|
||||
<!-- 초기화 스크립트 -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await pageManager.initializePage('new_feature');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 3단계: 페이지 모듈 생성
|
||||
```javascript
|
||||
// static/js/modules/new-feature/new-feature.js
|
||||
class NewFeatureModule extends BasePageModule {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.data = [];
|
||||
this.currentView = 'list';
|
||||
}
|
||||
|
||||
/**
|
||||
* 모듈 초기화
|
||||
*/
|
||||
async initialize() {
|
||||
try {
|
||||
this.showLoading('main-content', '새 기능을 로드하는 중...');
|
||||
|
||||
// 데이터 로드
|
||||
await this.loadData();
|
||||
|
||||
// UI 렌더링
|
||||
this.render();
|
||||
|
||||
// 이벤트 바인딩
|
||||
this.bindEvents();
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
} catch (error) {
|
||||
console.error('NewFeatureModule 초기화 실패:', error);
|
||||
this.showError('main-content', '새 기능을 로드할 수 없습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터 로드
|
||||
*/
|
||||
async loadData() {
|
||||
try {
|
||||
// API 호출 예시
|
||||
this.data = await NewFeatureAPI.getAll();
|
||||
} catch (error) {
|
||||
console.error('데이터 로드 실패:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UI 렌더링
|
||||
*/
|
||||
render() {
|
||||
const container = document.getElementById('main-content');
|
||||
container.innerHTML = this.generateHTML();
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML 생성
|
||||
*/
|
||||
generateHTML() {
|
||||
return `
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900">새 기능</h1>
|
||||
<p class="text-gray-600 mt-1">새 기능에 대한 설명</p>
|
||||
</div>
|
||||
|
||||
<!-- 액션 버튼 -->
|
||||
<div class="mb-6">
|
||||
<button id="add-btn" class="btn-primary px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
|
||||
<i class="fas fa-plus mr-2"></i>새 항목 추가
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 데이터 목록 -->
|
||||
<div id="data-list" class="bg-white rounded-lg shadow">
|
||||
${this.generateDataList()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터 목록 HTML 생성
|
||||
*/
|
||||
generateDataList() {
|
||||
if (this.data.length === 0) {
|
||||
return `
|
||||
<div class="p-8 text-center text-gray-500">
|
||||
<i class="fas fa-inbox text-4xl mb-4"></i>
|
||||
<p>데이터가 없습니다.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
제목
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
생성일
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
액션
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
${this.data.map(item => this.generateDataRow(item)).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터 행 HTML 생성
|
||||
*/
|
||||
generateDataRow(item) {
|
||||
return `
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
${item.title}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
${new Date(item.created_at).toLocaleDateString()}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<button onclick="newFeatureModule.editItem(${item.id})"
|
||||
class="text-blue-600 hover:text-blue-900 mr-3">
|
||||
수정
|
||||
</button>
|
||||
<button onclick="newFeatureModule.deleteItem(${item.id})"
|
||||
class="text-red-600 hover:text-red-900">
|
||||
삭제
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이벤트 바인딩
|
||||
*/
|
||||
bindEvents() {
|
||||
const addBtn = document.getElementById('add-btn');
|
||||
if (addBtn) {
|
||||
this.addEventListener(addBtn, 'click', () => this.showAddModal());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 항목 추가 모달 표시
|
||||
*/
|
||||
showAddModal() {
|
||||
// 모달 구현
|
||||
console.log('새 항목 추가 모달 표시');
|
||||
}
|
||||
|
||||
/**
|
||||
* 항목 수정
|
||||
*/
|
||||
editItem(id) {
|
||||
console.log('항목 수정:', id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 항목 삭제
|
||||
*/
|
||||
async deleteItem(id) {
|
||||
if (confirm('정말 삭제하시겠습니까?')) {
|
||||
try {
|
||||
await NewFeatureAPI.delete(id);
|
||||
await this.loadData();
|
||||
this.render();
|
||||
} catch (error) {
|
||||
alert('삭제에 실패했습니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// API 정의
|
||||
const NewFeatureAPI = {
|
||||
getAll: () => apiRequest('/new-feature/'),
|
||||
get: (id) => apiRequest(`/new-feature/${id}`),
|
||||
create: (data) => apiRequest('/new-feature/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
update: (id, data) => apiRequest(`/new-feature/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
delete: (id) => apiRequest(`/new-feature/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
};
|
||||
|
||||
// 전역 인스턴스 생성
|
||||
window.newFeatureModule = null;
|
||||
|
||||
// 페이지 매니저에 모듈 등록
|
||||
if (window.pageManager) {
|
||||
window.pageManager.createPageModule = function(pageId, options) {
|
||||
switch (pageId) {
|
||||
case 'new_feature':
|
||||
window.newFeatureModule = new NewFeatureModule(options);
|
||||
return window.newFeatureModule;
|
||||
// ... 기존 케이스들
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 4단계: 공통 헤더에 메뉴 추가
|
||||
```javascript
|
||||
// static/js/components/common-header.js의 initMenuItems()에 추가
|
||||
{
|
||||
id: 'new_feature',
|
||||
title: '새 기능',
|
||||
icon: 'fas fa-star',
|
||||
url: '/new-feature.html',
|
||||
pageName: 'new_feature',
|
||||
color: 'text-yellow-600',
|
||||
bgColor: 'bg-yellow-50 hover:bg-yellow-100'
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI/UX 가이드라인
|
||||
|
||||
### 1. 색상 팔레트
|
||||
- **Primary**: Blue (blue-600, blue-700)
|
||||
- **Success**: Green (green-600, green-700)
|
||||
- **Warning**: Yellow (yellow-600, yellow-700)
|
||||
- **Danger**: Red (red-600, red-700)
|
||||
- **Info**: Purple (purple-600, purple-700)
|
||||
- **Gray**: Gray (gray-500, gray-600, gray-700)
|
||||
|
||||
### 2. 컴포넌트 스타일
|
||||
```css
|
||||
/* 버튼 */
|
||||
.btn-primary { @apply px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors; }
|
||||
.btn-secondary { @apply px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors; }
|
||||
.btn-success { @apply px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors; }
|
||||
.btn-danger { @apply px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors; }
|
||||
|
||||
/* 입력 필드 */
|
||||
.input-field { @apply w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500; }
|
||||
|
||||
/* 카드 */
|
||||
.card { @apply bg-white rounded-lg shadow-sm border border-gray-200; }
|
||||
.card-header { @apply px-6 py-4 border-b border-gray-200; }
|
||||
.card-body { @apply px-6 py-4; }
|
||||
```
|
||||
|
||||
### 3. 반응형 디자인
|
||||
- **Mobile First**: 모바일부터 디자인 시작
|
||||
- **Breakpoints**: sm(640px), md(768px), lg(1024px), xl(1280px)
|
||||
- **Grid System**: Tailwind CSS Grid 사용
|
||||
|
||||
---
|
||||
|
||||
## 🔧 API 개발 가이드
|
||||
|
||||
### 1. 백엔드 라우터 생성
|
||||
```python
|
||||
# backend/routers/new_feature.py
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from database.database import get_db
|
||||
from database.models import User
|
||||
from routers.auth import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/api/new-feature", tags=["new-feature"])
|
||||
|
||||
@router.get("/")
|
||||
async def get_all_items(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""모든 항목 조회"""
|
||||
# 구현 내용
|
||||
pass
|
||||
|
||||
@router.post("/")
|
||||
async def create_item(
|
||||
item_data: dict,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""새 항목 생성"""
|
||||
# 구현 내용
|
||||
pass
|
||||
```
|
||||
|
||||
### 2. 메인 앱에 라우터 등록
|
||||
```python
|
||||
# backend/main.py
|
||||
from routers import new_feature
|
||||
|
||||
app.include_router(new_feature.router)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 코드 품질 가이드
|
||||
|
||||
### 1. 에러 처리
|
||||
```javascript
|
||||
// 좋은 예
|
||||
try {
|
||||
const data = await API.getData();
|
||||
this.processData(data);
|
||||
} catch (error) {
|
||||
console.error('데이터 로드 실패:', error);
|
||||
this.showError('데이터를 불러올 수 없습니다.');
|
||||
}
|
||||
|
||||
// 나쁜 예
|
||||
const data = await API.getData(); // 에러 처리 없음
|
||||
```
|
||||
|
||||
### 2. 로딩 상태 관리
|
||||
```javascript
|
||||
// 좋은 예
|
||||
async loadData() {
|
||||
this.showLoading('data-container');
|
||||
try {
|
||||
const data = await API.getData();
|
||||
this.renderData(data);
|
||||
} catch (error) {
|
||||
this.showError('data-container', '데이터 로드 실패');
|
||||
}
|
||||
}
|
||||
|
||||
// 나쁜 예
|
||||
async loadData() {
|
||||
const data = await API.getData(); // 로딩 상태 없음
|
||||
this.renderData(data);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 메모리 관리
|
||||
```javascript
|
||||
// 좋은 예 - BasePageModule 사용
|
||||
class MyModule extends BasePageModule {
|
||||
bindEvents() {
|
||||
const button = document.getElementById('my-button');
|
||||
this.addEventListener(button, 'click', this.handleClick.bind(this));
|
||||
}
|
||||
|
||||
// cleanup()은 BasePageModule에서 자동 처리
|
||||
}
|
||||
|
||||
// 나쁜 예 - 수동 이벤트 관리
|
||||
class MyModule {
|
||||
bindEvents() {
|
||||
document.getElementById('my-button').addEventListener('click', this.handleClick);
|
||||
// 이벤트 리스너 제거 코드 없음
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 테스트 가이드
|
||||
|
||||
### 1. 기능 테스트 체크리스트
|
||||
- [ ] 페이지 로드 정상 작동
|
||||
- [ ] 권한 체크 정상 작동
|
||||
- [ ] CRUD 기능 정상 작동
|
||||
- [ ] 에러 처리 정상 작동
|
||||
- [ ] 반응형 디자인 정상 작동
|
||||
- [ ] 브라우저 호환성 확인
|
||||
|
||||
### 2. 성능 테스트
|
||||
- [ ] 페이지 로드 시간 < 3초
|
||||
- [ ] API 응답 시간 < 1초
|
||||
- [ ] 메모리 누수 없음
|
||||
- [ ] 모바일 성능 최적화
|
||||
|
||||
---
|
||||
|
||||
## 📚 참고 자료
|
||||
|
||||
### 1. 기존 모듈 참고
|
||||
- `static/js/components/common-header.js` - 공통 컴포넌트 예시
|
||||
- `static/js/core/page-manager.js` - 페이지 관리 예시
|
||||
- `static/js/core/permissions.js` - 권한 시스템 예시
|
||||
|
||||
### 2. 외부 라이브러리
|
||||
- **Tailwind CSS**: https://tailwindcss.com/docs
|
||||
- **Font Awesome**: https://fontawesome.com/icons
|
||||
- **FastAPI**: https://fastapi.tiangolo.com/
|
||||
|
||||
### 3. 코딩 컨벤션
|
||||
- **JavaScript**: Airbnb Style Guide
|
||||
- **Python**: PEP 8
|
||||
- **HTML/CSS**: Google Style Guide
|
||||
|
||||
---
|
||||
|
||||
**작성일**: 2025-10-25
|
||||
**버전**: 1.0
|
||||
**작성자**: AI Assistant
|
||||
|
||||
> 이 가이드는 프로젝트 발전에 따라 지속적으로 업데이트됩니다.
|
||||
Reference in New Issue
Block a user