- 데이터베이스 스키마 및 변경 로그 문서화 - 신규 페이지 개발 가이드 작성 - 모듈 아키텍처 설계 문서 추가 - 성능 최적화 전략 문서화 - 리팩토링 계획 및 진행 상황 정리 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 테스트 페이지
512 lines
14 KiB
Markdown
512 lines
14 KiB
Markdown
# 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
|
|
|
|
> 이 가이드는 프로젝트 발전에 따라 지속적으로 업데이트됩니다.
|