# 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
새 기능 - 작업보고서
```
### 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 `
${this.generateDataList()}
`;
}
/**
* 데이터 목록 HTML 생성
*/
generateDataList() {
if (this.data.length === 0) {
return `
`;
}
return `
|
제목
|
생성일
|
액션
|
${this.data.map(item => this.generateDataRow(item)).join('')}
`;
}
/**
* 데이터 행 HTML 생성
*/
generateDataRow(item) {
return `
|
${item.title}
|
${new Date(item.created_at).toLocaleDateString()}
|
|
`;
}
/**
* 이벤트 바인딩
*/
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
> 이 가이드는 프로젝트 발전에 따라 지속적으로 업데이트됩니다.