# 동적행 생성 시스템 (Dynamic Row Generation System)

## 개요
이 문서는 웹 애플리케이션에서 동적으로 테이블 행을 생성, 삭제, 복사할 수 있는 시스템의 구현 방법을 설명합니다.

## 주요 기능
- **행 추가**: 기존 행 아래에 새로운 행 삽입
- **행 삭제**: 선택된 행 제거
- **행 복사**: 기존 행의 내용을 복사하여 새로운 행 생성
- **동적 ID 관리**: 자동으로 증가하는 행 인덱스 관리

## HTML 구조

### 기본 테이블 구조
```html
<table class="table table-bordered align-middle text-center table-sm" id="taskTable">
    <thead class="table-light">
        <tr>
            <th scope="col" style="width: 10%;">관리</th>
            <th scope="col" style="width: 60%;">할일</th>
            <th scope="col" style="width: 8%;">완료</th>
            <th scope="col" style="width: 8%;">완료일</th>
            <th scope="col" style="width: 8%;">경과</th>
        </tr>
    </thead>
    <tbody id="taskTableBody">
        <!-- 동적으로 생성되는 행들이 여기에 들어갑니다 -->
    </tbody>
</table>
```

### 버튼 그룹 구조
```html
<div class="btn-group btn-group-sm" role="group" style="gap: 1px;">
    <button type="button" class="btn btn-outline-primary btn-sm p-0" 
            style="width: 20px; height: 20px; font-size: 12px;" 
            onclick="addRowAfter(<?= $i ?>)" title="아래에 행 추가">
        <i class="bi bi-plus"></i>
    </button>
    <button type="button" class="btn btn-outline-success btn-sm p-0" 
            style="width: 20px; height: 20px; font-size: 12px;" 
            onclick="copyRow(<?= $i ?>)" title="행 복사">
        <i class="bi bi-files"></i>
    </button>
    <button type="button" class="btn btn-outline-danger btn-sm p-0" 
            style="width: 20px; height: 20px; font-size: 12px;" 
            onclick="deleteRow(<?= $i ?>)" title="행 삭제">
        <i class="bi bi-dash"></i>
    </button>
</div>
```

### 개별 행 구조
```html
<tr class="task-row align-middle" data-row="<?= $i ?>" data-unique-id="<?= $unique_id ?>">
    <td class="text-center align-middle">
        <!-- 버튼 그룹이 들어가는 셀 -->
    </td>
    <td class="text-center align-middle">
        <div class="d-flex justify-content-start align-items-center w-100">
            <input type="text" name="tasks[<?= $i ?>][task_content]" 
                   class="form-control form-control-sm task-content-input flex-grow-1" 
                   placeholder="할일을 입력하세요" 
                   value="<?= htmlspecialchars($task['task_content'] ?? '') ?>">
            <input type="hidden" name="tasks[<?= $i ?>][unique_id]" value="<?= $unique_id ?>">
            <input type="hidden" name="tasks[<?= $i ?>][is_pending]" value="<?= $is_pending ? '1' : '0' ?>">
        </div>
    </td>
    <td class="text-center align-middle">
        <div class="form-check d-flex justify-content-center align-middle align-items-center">
            <input class="form-check-input task-checkbox" type="checkbox" 
                   name="tasks[<?= $i ?>][is_completed]" value="1" 
                   <?= ($task['is_completed'] ?? false) ? 'checked' : '' ?> 
                   onchange="updateCompletionDate(this)">
        </div>
    </td>
    <td class="text-center align-middle">
        <input type="date" name="tasks[<?= $i ?>][completion_date]" 
               class="form-control form-control-sm completion-date-input" 
               value="<?= $task['completion_date'] ?? '' ?>">
    </td>
    <td class="text-center align-middle">
        <!-- 경과일 표시 영역 -->
    </td>
</tr>
```

## JavaScript 함수들

### 1. 행 추가 함수 (addRowAfter)
```javascript
function addRowAfter(rowIndex) {
    const tbody = document.getElementById('taskTableBody');
    const rows = tbody.querySelectorAll('.task-row');
    const newRowIndex = getNextRowIndex();
    
    // 새로운 행 HTML 생성
    const newRow = createTaskRow(newRowIndex);
    
    // 지정된 행 다음에 삽입
    if (rowIndex < rows.length - 1) {
        rows[rowIndex].insertAdjacentHTML('afterend', newRow);
    } else {
        tbody.insertAdjacentHTML('beforeend', newRow);
    }
    
    // 행 인덱스 재정렬
    reorderRowIndexes();
    
    // 통계 업데이트
    updateTaskStatistics();
}

function createTaskRow(rowIndex) {
    return `
        <tr class="task-row align-middle" data-row="${rowIndex}" data-unique-id="">
            <td class="text-center align-middle">
                <div class="d-flex align-items-center justify-content-center">
                    <div class="btn-group btn-group-sm" role="group" style="gap: 1px;">
                        <button type="button" class="btn btn-outline-primary btn-sm p-0" 
                                style="width: 20px; height: 20px; font-size: 12px;" 
                                onclick="addRowAfter(${rowIndex})" title="아래에 행 추가">
                            <i class="bi bi-plus"></i>
                        </button>
                        <button type="button" class="btn btn-outline-success btn-sm p-0" 
                                style="width: 20px; height: 20px; font-size: 12px;" 
                                onclick="copyRow(${rowIndex})" title="행 복사">
                            <i class="bi bi-files"></i>
                        </button>
                        <button type="button" class="btn btn-outline-danger btn-sm p-0" 
                                style="width: 20px; height: 20px; font-size: 12px;" 
                                onclick="deleteRow(${rowIndex})" title="행 삭제">
                            <i class="bi bi-dash"></i>
                        </button>
                    </div>
                </div>
            </td>
            <td class="text-center align-middle">
                <div class="d-flex justify-content-start align-items-center w-100">
                    <input type="text" name="tasks[${rowIndex}][task_content]" 
                           class="form-control form-control-sm task-content-input flex-grow-1" 
                           placeholder="할일을 입력하세요" value="">
                    <input type="hidden" name="tasks[${rowIndex}][unique_id]" value="">
                    <input type="hidden" name="tasks[${rowIndex}][is_pending]" value="0">
                </div>
            </td>
            <td class="text-center align-middle">
                <div class="form-check d-flex justify-content-center align-middle align-items-center">
                    <input class="form-check-input task-checkbox" type="checkbox" 
                           name="tasks[${rowIndex}][is_completed]" value="1" 
                           onchange="updateCompletionDate(this)">
                </div>
            </td>
            <td class="text-center align-middle">
                <input type="date" name="tasks[${rowIndex}][completion_date]" 
                       class="form-control form-control-sm completion-date-input" 
                       value="" readonly>
            </td>
            <td class="text-center align-middle">
                <span class="elapsed-days-display" data-original-date="">-</span>
            </td>
        </tr>
    `;
}
```

### 2. 행 삭제 함수 (deleteRow)
```javascript
function deleteRow(rowIndex) {
    const tbody = document.getElementById('taskTableBody');
    const rows = tbody.querySelectorAll('.task-row');
    
    if (rows.length <= 1) {
        alert('최소 하나의 행은 유지해야 합니다.');
        return;
    }
    
    // 행 삭제 확인
    if (!confirm('이 행을 삭제하시겠습니까?')) {
        return;
    }
    
    // 해당 행 삭제
    const targetRow = tbody.querySelector(`tr[data-row="${rowIndex}"]`);
    if (targetRow) {
        targetRow.remove();
    }
    
    // 행 인덱스 재정렬
    reorderRowIndexes();
    
    // 통계 업데이트
    updateTaskStatistics();
}
```

### 3. 행 복사 함수 (copyRow)
```javascript
function copyRow(rowIndex) {
    const tbody = document.getElementById('taskTableBody');
    const sourceRow = tbody.querySelector(`tr[data-row="${rowIndex}"]`);
    const newRowIndex = getNextRowIndex();
    
    if (!sourceRow) return;
    
    // 소스 행의 데이터 수집
    const taskContent = sourceRow.querySelector('input[name*="[task_content]"]').value;
    const isCompleted = sourceRow.querySelector('input[name*="[is_completed]"]').checked;
    const completionDate = sourceRow.querySelector('input[name*="[completion_date]"]').value;
    
    // 새로운 행 생성 (복사된 데이터 포함)
    const newRow = createTaskRowWithData(newRowIndex, taskContent, isCompleted, completionDate);
    
    // 소스 행 다음에 삽입
    sourceRow.insertAdjacentHTML('afterend', newRow);
    
    // 행 인덱스 재정렬
    reorderRowIndexes();
    
    // 통계 업데이트
    updateTaskStatistics();
}

function createTaskRowWithData(rowIndex, taskContent, isCompleted, completionDate) {
    const checkedAttr = isCompleted ? 'checked' : '';
    const readonlyAttr = isCompleted ? '' : 'readonly';
    
    return `
        <tr class="task-row align-middle" data-row="${rowIndex}" data-unique-id="">
            <td class="text-center align-middle">
                <div class="d-flex align-items-center justify-content-center">
                    <div class="btn-group btn-group-sm" role="group" style="gap: 1px;">
                        <button type="button" class="btn btn-outline-primary btn-sm p-0" 
                                style="width: 20px; height: 20px; font-size: 12px;" 
                                onclick="addRowAfter(${rowIndex})" title="아래에 행 추가">
                            <i class="bi bi-plus"></i>
                        </button>
                        <button type="button" class="btn btn-outline-success btn-sm p-0" 
                                style="width: 20px; height: 20px; font-size: 12px;" 
                                onclick="copyRow(${rowIndex})" title="행 복사">
                            <i class="bi bi-files"></i>
                        </button>
                        <button type="button" class="btn btn-outline-danger btn-sm p-0" 
                                style="width: 20px; height: 20px; font-size: 12px;" 
                                onclick="deleteRow(${rowIndex})" title="행 삭제">
                            <i class="bi bi-dash"></i>
                        </button>
                    </div>
                </div>
            </td>
            <td class="text-center align-middle">
                <div class="d-flex justify-content-start align-items-center w-100">
                    <input type="text" name="tasks[${rowIndex}][task_content]" 
                           class="form-control form-control-sm task-content-input flex-grow-1" 
                           placeholder="할일을 입력하세요" value="${taskContent}">
                    <input type="hidden" name="tasks[${rowIndex}][unique_id]" value="">
                    <input type="hidden" name="tasks[${rowIndex}][is_pending]" value="0">
                </div>
            </td>
            <td class="text-center align-middle">
                <div class="form-check d-flex justify-content-center align-middle align-items-center">
                    <input class="form-check-input task-checkbox" type="checkbox" 
                           name="tasks[${rowIndex}][is_completed]" value="1" 
                           ${checkedAttr} onchange="updateCompletionDate(this)">
                </div>
            </td>
            <td class="text-center align-middle">
                <input type="date" name="tasks[${rowIndex}][completion_date]" 
                       class="form-control form-control-sm completion-date-input" 
                       value="${completionDate}" ${readonlyAttr}>
            </td>
            <td class="text-center align-middle">
                <span class="elapsed-days-display" data-original-date="">-</span>
            </td>
        </tr>
    `;
}
```

### 4. 유틸리티 함수들
```javascript
// 다음 행 인덱스 가져오기
function getNextRowIndex() {
    const tbody = document.getElementById('taskTableBody');
    const rows = tbody.querySelectorAll('.task-row');
    return rows.length;
}

// 행 인덱스 재정렬
function reorderRowIndexes() {
    const tbody = document.getElementById('taskTableBody');
    const rows = tbody.querySelectorAll('.task-row');
    
    rows.forEach((row, index) => {
        // data-row 속성 업데이트
        row.setAttribute('data-row', index);
        
        // 모든 input의 name 속성 업데이트
        const inputs = row.querySelectorAll('input');
        inputs.forEach(input => {
            const name = input.getAttribute('name');
            if (name && name.includes('[')) {
                const newName = name.replace(/\[\d+\]/, `[${index}]`);
                input.setAttribute('name', newName);
            }
        });
        
        // 버튼의 onclick 속성 업데이트
        const buttons = row.querySelectorAll('button');
        buttons.forEach(button => {
            const onclick = button.getAttribute('onclick');
            if (onclick) {
                const newOnclick = onclick.replace(/\(\d+\)/g, `(${index})`);
                button.setAttribute('onclick', newOnclick);
            }
        });
    });
}

// 완료일 업데이트 함수
function updateCompletionDate(checkbox) {
    const row = checkbox.closest('tr');
    const completionDateInput = row.querySelector('input[name*="[completion_date]"]');
    
    if (checkbox.checked) {
        completionDateInput.value = new Date().toISOString().split('T')[0];
        completionDateInput.removeAttribute('readonly');
    } else {
        completionDateInput.value = '';
        completionDateInput.setAttribute('readonly', 'readonly');
    }
    
    // 통계 업데이트
    updateTaskStatistics();
}

// 통계 업데이트 함수
function updateTaskStatistics() {
    const tbody = document.getElementById('taskTableBody');
    const rows = tbody.querySelectorAll('.task-row');
    
    let totalTasks = 0;
    let completedTasks = 0;
    
    rows.forEach(row => {
        const taskContent = row.querySelector('input[name*="[task_content]"]').value.trim();
        const isCompleted = row.querySelector('input[name*="[is_completed]"]').checked;
        
        if (taskContent !== '') {
            totalTasks++;
            if (isCompleted) {
                completedTasks++;
            }
        }
    });
    
    const pendingTasks = totalTasks - completedTasks;
    const completionRate = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
    
    // 통계 표시 업데이트
    document.getElementById('totalTasks').textContent = totalTasks;
    document.getElementById('completedTasks').textContent = completedTasks;
    document.getElementById('pendingTasks').textContent = pendingTasks;
    document.getElementById('completionRate').textContent = completionRate + '%';
}
```

## CSS 스타일

### 버튼 그룹 스타일
```css
.btn-group-sm .btn {
    border-radius: 0.2rem;
    font-size: 0.875rem;
    line-height: 1.5;
    padding: 0.25rem 0.5rem;
}

.btn-group .btn:not(:last-child):not(.dropdown-toggle) {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

.btn-group .btn:not(:first-child) {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

/* 작은 버튼 스타일 */
.btn-sm {
    padding: 0.25rem 0.5rem;
    font-size: 0.875rem;
    border-radius: 0.2rem;
}

/* 아이콘 버튼 스타일 */
.btn i {
    font-size: 12px;
}
```

### 테이블 스타일
```css
.table-sm td, .table-sm th {
    padding: 0.3rem;
}

.task-row {
    transition: background-color 0.2s ease;
}

.task-row:hover {
    background-color: rgba(0, 123, 255, 0.05);
}

.form-control-sm {
    height: calc(1.5em + 0.5rem + 2px);
    padding: 0.25rem 0.5rem;
    font-size: 0.875rem;
    line-height: 1.5;
    border-radius: 0.2rem;
}
```

## 사용 방법

### 1. HTML에 테이블 구조 추가
```html
<!-- 위의 HTML 구조를 복사하여 사용 -->
```

### 2. JavaScript 함수들 추가
```html
<script>
// 위의 모든 JavaScript 함수들을 복사하여 사용
</script>
```

### 3. CSS 스타일 추가
```html
<style>
/* 위의 CSS 스타일들을 복사하여 사용 */
</style>
```

### 4. Bootstrap 및 아이콘 라이브러리 포함
```html
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">

<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" rel="stylesheet">

<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
```

## 주의사항

1. **최소 행 유지**: 최소 하나의 행은 항상 유지되어야 합니다.
2. **인덱스 관리**: 행이 삭제되거나 추가될 때 인덱스가 자동으로 재정렬됩니다.
3. **데이터 무결성**: 폼 제출 시 모든 행의 데이터가 올바르게 전송되도록 name 속성이 관리됩니다.
4. **브라우저 호환성**: ES6 문법을 사용하므로 최신 브라우저에서 동작합니다.

## 확장 가능한 기능

1. **드래그 앤 드롭**: 행 순서 변경
2. **일괄 작업**: 여러 행 선택 및 일괄 처리
3. **검색 및 필터링**: 특정 조건에 맞는 행만 표시
4. **데이터 유효성 검사**: 입력 데이터 검증
5. **자동 저장**: 변경사항 자동 저장

이 시스템을 사용하면 동적으로 테이블 행을 관리할 수 있는 완전한 기능을 갖춘 인터페이스를 구축할 수 있습니다.
