Files
M-Project/DEVELOPMENT_GUIDE.md
Hyungi Ahn d3333c4dc2 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 테스트 페이지
2025-10-25 09:01:54 +09:00

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. 외부 라이브러리

3. 코딩 컨벤션

  • JavaScript: Airbnb Style Guide
  • Python: PEP 8
  • HTML/CSS: Google Style Guide

작성일: 2025-10-25
버전: 1.0
작성자: AI Assistant

이 가이드는 프로젝트 발전에 따라 지속적으로 업데이트됩니다.