- 데이터베이스 스키마 및 변경 로그 문서화 - 신규 페이지 개발 가이드 작성 - 모듈 아키텍처 설계 문서 추가 - 성능 최적화 전략 문서화 - 리팩토링 계획 및 진행 상황 정리 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 테스트 페이지
14 KiB
14 KiB
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단계: 권한 정의
// backend/routers/page_permissions.py의 DEFAULT_PAGES에 추가
'new_feature': {'title': '새 기능', 'default_access': false}
2단계: 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단계: 페이지 모듈 생성
// 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단계: 공통 헤더에 메뉴 추가
// 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. 컴포넌트 스타일
/* 버튼 */
.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. 백엔드 라우터 생성
# 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. 메인 앱에 라우터 등록
# backend/main.py
from routers import new_feature
app.include_router(new_feature.router)
📝 코드 품질 가이드
1. 에러 처리
// 좋은 예
try {
const data = await API.getData();
this.processData(data);
} catch (error) {
console.error('데이터 로드 실패:', error);
this.showError('데이터를 불러올 수 없습니다.');
}
// 나쁜 예
const data = await API.getData(); // 에러 처리 없음
2. 로딩 상태 관리
// 좋은 예
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. 메모리 관리
// 좋은 예 - 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
이 가이드는 프로젝트 발전에 따라 지속적으로 업데이트됩니다.