Files
sam-manage/docs/TABLE_LAYOUT_STANDARD.md
김보곤 389852dd2f docs:React 테이블 컬럼 비율 설정 가이드 추가
- colgroup을 사용한 명시적 컬럼 너비 지정 방법
- Blade에서 React 스타일 객체 이스케이프 (@{{ }})
- 입력 테이블 권장 컬럼 비율 가이드 (품목명 30%, 수량 60px 등)
- 전체 예제 코드 포함

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 08:06:35 +09:00

26 KiB

MNG 테이블 레이아웃 표준

기준 페이지: /permissions (권한 관리) 작성일: 2025-11-25 목적: mng 프로젝트의 모든 테이블 페이지에서 일관된 레이아웃과 UX를 제공


📋 목차

  1. 페이지 구조
  2. 페이지 헤더
  3. 필터 영역
  4. 테이블 구조
  5. 페이지네이션
  6. 기술 스택
  7. 체크리스트

1. 페이지 구조

1.1 전체 레이아웃 순서

@extends('layouts.app')

@section('content')
    <!-- ① 페이지 헤더 -->
    <div class="flex justify-between items-center mb-6">
        <!-- 제목 + 액션 버튼 -->
    </div>

    <!-- ② 필터 영역 (선택사항) -->
    <div class="bg-white rounded-lg shadow-sm p-4 mb-6">
        <!-- 검색, 필터 폼 -->
    </div>

    <!-- ③ 테이블 영역 (HTMX) -->
    <div id="{resource}-table"
         hx-get="/api/admin/{resource}"
         hx-trigger="load, filterSubmit from:body"
         hx-include="#filterForm"
         class="bg-white rounded-lg shadow-sm overflow-hidden">
        <!-- 로딩 스피너 -->
        <!-- 테이블 partial 로드됨 -->
    </div>
@endsection

1.2 파일 구조

resources/views/{resource}/
├── index.blade.php              # 메인 페이지 (레이아웃만)
├── create.blade.php             # 생성 폼
├── edit.blade.php               # 수정 폼
└── partials/
    └── table.blade.php          # 테이블 + 페이지네이션

2. 페이지 헤더

2.1 기본 구조

<div class="flex justify-between items-center mb-6">
    <h1 class="text-2xl font-bold text-gray-800">{페이지 제목}</h1>
    <a href="{{ route('{resource}.create') }}"
       class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition">
        + {액션 버튼 레이블}
    </a>
</div>

2.2 스타일 규칙

  • 제목: text-2xl font-bold text-gray-800
  • 액션 버튼: bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition
  • 간격: mb-6 (하단 여백)

3. 필터 영역

3.1 기본 구조

<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
    <form id="filterForm" class="flex gap-4">
        <!-- 검색 입력 -->
        <div class="flex-1">
            <input type="text"
                   name="search"
                   placeholder="{항목명}으로 검색..."
                   class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
        </div>

        <!-- 드롭다운 필터 (선택사항) -->
        <div class="w-48">
            <select name="{filter_name}"
                    class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
                <option value="">전체 {필터명}</option>
                <!-- 옵션들 -->
            </select>
        </div>

        <!-- 검색 버튼 -->
        <button type="submit"
                class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded-lg transition">
            검색
        </button>
    </form>
</div>

3.2 스타일 규칙

  • 컨테이너: bg-white rounded-lg shadow-sm p-4 mb-6
  • : flex gap-4 (가로 배치, 간격 4)
  • 검색 입력: flex-1 (가변 폭)
  • 드롭다운: w-48 (고정 폭 192px)
  • 버튼: bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded-lg transition

3.3 JavaScript 이벤트

document.getElementById('filterForm').addEventListener('submit', function(e) {
    e.preventDefault();
    htmx.trigger('#{resource}-table', 'filterSubmit');
});

4. 테이블 구조

4.1 HTMX 컨테이너

<div id="{resource}-table"
     hx-get="/api/admin/{resource}"
     hx-trigger="load, filterSubmit from:body"
     hx-include="#filterForm"
     hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
     class="bg-white rounded-lg shadow-sm overflow-hidden">
    <!-- 로딩 스피너 -->
    <div class="flex justify-center items-center p-12">
        <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
    </div>
</div>

4.2 테이블 Partial (partials/table.blade.php)

<div class="overflow-x-auto">
    <table class="min-w-full divide-y divide-gray-200">
        <thead class="bg-gray-50">
            <tr>
                <th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
                    {컬럼명}
                </th>
                <!-- 추가 컬럼들 -->
                <th class="px-4 py-2 text-right text-sm font-semibold text-gray-700 uppercase tracking-wider">
                    액션
                </th>
            </tr>
        </thead>
        <tbody class="bg-white divide-y divide-gray-200">
            @forelse($items as $item)
            <tr>
                <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
                    {{ $item->속성 }}
                </td>
                <!-- 추가 컬럼들 -->
                <td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium">
                    <a href="{{ route('{resource}.edit', $item->id) }}"
                       class="text-blue-600 hover:text-blue-900 mr-3">
                        수정
                    </a>
                    <button onclick="confirmDelete({{ $item->id }}, '{{ $item->name }}')"
                            class="text-red-600 hover:text-red-900">
                        삭제
                    </button>
                </td>
            </tr>
            @empty
            <tr>
                <td colspan="{컬럼수}" class="px-6 py-12 text-center text-gray-500">
                    등록된 {항목명}이(가) 없습니다.
                </td>
            </tr>
            @endforelse
        </tbody>
    </table>
</div>

<!-- 페이지네이션 -->
@include('partials.pagination', [
    'paginator' => $items,
    'target' => '#{resource}-table',
    'includeForm' => '#filterForm'
])

4.3 스타일 규칙

테이블 헤더

  • 배경: bg-gray-50
  • 텍스트: text-sm font-semibold text-gray-700 uppercase tracking-wider
  • 정렬: text-left (일반), text-right (액션)
  • 패딩: px-4 py-2

테이블 본문

  • 행 구분: divide-y divide-gray-200
  • 셀 패딩: px-4 py-3
  • 텍스트: text-sm text-gray-900 (일반), text-gray-500 (보조)
  • 공백 처리: whitespace-nowrap (줄바꿈 방지)

액션 버튼

  • 수정: text-blue-600 hover:text-blue-900 mr-3
  • 삭제: text-red-600 hover:text-red-900

4.4 배지 스타일 (선택사항)

Inline 스타일 배지

<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.5rem; font-size: 0.75rem; font-weight: 500; border-radius: 0.375rem; background-color: rgb(219 234 254); color: rgb(30 64 175);">
    {배지 텍스트}
</span>

배지 색상 시스템

용도 배경색 (RGB) 텍스트색 (RGB) 사용 예시
Primary (파란색) rgb(219 234 254) rgb(30 64 175) Guard, 기본 태그
Success (초록색) rgb(220 252 231) rgb(21 128 61) 역할, 활성 상태
Warning (노란색) rgb(254 249 195) rgb(133 77 14) 부서, 경고
Danger (빨간색) rgb(254 202 202) rgb(153 27 27) 삭제 권한
Gray (회색) rgb(243 244 246) rgb(31 41 55) 메뉴 태그, 중립
Orange (주황색) rgb(254 215 170) rgb(154 52 18) 수정 권한
Purple (보라색) rgb(233 213 255) rgb(107 33 168) 승인 권한
Cyan (청록색) rgb(207 250 254) rgb(14 116 144) 내보내기 권한

Tailwind 클래스 배지 (대안)

<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded bg-blue-100 text-blue-800">
    {배지 텍스트}
</span>

4.5 Empty State

@empty
<tr>
    <td colspan="{컬럼수}" class="px-6 py-12 text-center text-gray-500">
        등록된 {항목명}이(가) 없습니다.
    </td>
</tr>
@endforelse

5. 페이지네이션

5.1 Include 방식

@include('partials.pagination', [
    'paginator' => $items,
    'target' => '#{resource}-table',
    'includeForm' => '#filterForm'
])

5.2 페이지네이션 기능

데스크톱 (>=640px)

  • 전체 개수 표시: "전체 N개 중 X ~ Y"
  • 페이지당 항목 수 선택: 10/20/30/50/100/200/500개씩
  • 네비게이션 버튼:
    • 처음 (첫 페이지로)
    • 이전 (이전 페이지로)
    • 페이지 번호 (최대 10개 표시)
    • 다음 (다음 페이지로)
    • 끝 (마지막 페이지로)

모바일 (<640px)

  • 이전/다음 버튼만 표시
  • 간소화된 네비게이션

5.3 JavaScript 핸들러

// 페이지 변경
function handlePageChange(page) {
    const form = document.getElementById('filterForm');
    const formData = new FormData(form);
    formData.append('page', page);

    const params = new URLSearchParams(formData).toString();
    htmx.ajax('GET', `/api/admin/{resource}?${params}`, {
        target: '#{resource}-table',
        swap: 'innerHTML'
    });
}

// 페이지당 항목 수 변경
function handlePerPageChange(perPage) {
    const form = document.getElementById('filterForm');
    const formData = new FormData(form);
    formData.append('per_page', perPage);
    formData.append('page', 1); // 첫 페이지로 리셋

    const params = new URLSearchParams(formData).toString();
    htmx.ajax('GET', `/api/admin/{resource}?${params}`, {
        target: '#{resource}-table',
        swap: 'innerHTML'
    });
}

5.4 스타일 규칙

  • 컨테이너: bg-white px-4 py-3 border-t border-gray-200 sm:px-6
  • 전체 개수: text-sm text-gray-700, 숫자는 font-medium
  • 페이지당 항목 선택: px-3 py-1 border border-gray-300 rounded-lg text-sm
  • 버튼 (활성): bg-white text-gray-700 hover:bg-gray-50
  • 버튼 (비활성): bg-gray-100 text-gray-400 cursor-not-allowed
  • 현재 페이지: bg-blue-50 text-blue-600

6. 기술 스택

6.1 필수 라이브러리

<!-- HTMX -->
<script src="https://unpkg.com/htmx.org@1.9.10"></script>

<!-- Tailwind CSS (이미 레이아웃에 포함) -->

6.2 API 컨트롤러

namespace App\Http\Controllers\Api\Admin;

class {Resource}Controller extends Controller
{
    public function __construct(
        private {Resource}Service $service
    ) {}

    public function index(Request $request)
    {
        $items = $this->service->get{Resources}(
            $request->all(),
            $request->input('per_page', 20)
        );

        // HTMX 요청 시 부분 HTML 반환
        if ($request->header('HX-Request')) {
            return view('{resource}.partials.table', compact('items'));
        }

        // 일반 요청 시 JSON 반환
        return response()->json([
            'success' => true,
            'data' => $items->items(),
            'meta' => [
                'current_page' => $items->currentPage(),
                'total' => $items->total(),
                'per_page' => $items->perPage(),
                'last_page' => $items->lastPage(),
            ],
        ]);
    }
}

6.3 라우트

// web.php (화면)
Route::get('/{resource}', [{Resource}Controller::class, 'index'])
    ->name('{resource}.index');

// api.php (데이터)
Route::prefix('api/admin')->group(function () {
    Route::get('/{resource}', [Api\Admin\{Resource}Controller::class, 'index']);
    Route::post('/{resource}', [Api\Admin\{Resource}Controller::class, 'store']);
    Route::get('/{resource}/{id}', [Api\Admin\{Resource}Controller::class, 'show']);
    Route::put('/{resource}/{id}', [Api\Admin\{Resource}Controller::class, 'update']);
    Route::delete('/{resource}/{id}', [Api\Admin\{Resource}Controller::class, 'destroy']);
});

7. 체크리스트

7.1 페이지 생성 체크리스트

## 새 테이블 페이지 생성 체크리스트

### 파일 구조
- [ ] `resources/views/{resource}/index.blade.php` 생성
- [ ] `resources/views/{resource}/partials/table.blade.php` 생성
- [ ] `app/Http/Controllers/{Resource}Controller.php` 생성
- [ ] `app/Http/Controllers/Api/Admin/{Resource}Controller.php` 생성
- [ ] `app/Services/{Resource}Service.php` 생성

### 페이지 헤더
- [ ] 제목 (`text-2xl font-bold text-gray-800`)
- [ ] 액션 버튼 (`bg-blue-600 hover:bg-blue-700`)
- [ ] 하단 여백 (`mb-6`)

### 필터 영역 (선택사항)
- [ ] 검색 입력 (`flex-1`)
- [ ] 드롭다운 필터 (`w-48`)
- [ ] 검색 버튼 (`bg-gray-600`)
- [ ] JavaScript 이벤트 핸들러

### 테이블
- [ ] HTMX 컨테이너 (`hx-get`, `hx-trigger`, `hx-include`)
- [ ] 로딩 스피너
- [ ] 테이블 헤더 (`bg-gray-50`)
- [ ] 테이블 본문 (`divide-y divide-gray-200`)
- [ ] Empty State
- [ ] 액션 버튼 (수정, 삭제)

### 페이지네이션
- [ ] `@include('partials.pagination')` 추가
- [ ] `handlePageChange()` 함수 구현
- [ ] `handlePerPageChange()` 함수 구현

### API
- [ ] `index()` 메서드 (HTMX + JSON 분기)
- [ ] Service 계층 (비즈니스 로직)
- [ ] FormRequest (검증)
- [ ] 라우트 등록

### 테스트
- [ ] 필터 검색 동작 확인
- [ ] 페이지네이션 동작 확인
- [ ] 액션 버튼 동작 확인
- [ ] 반응형 레이아웃 확인 (모바일/데스크톱)

7.2 스타일 일관성 체크

## 스타일 일관성 체크리스트

### 색상
- [ ] Primary 버튼: `bg-blue-600 hover:bg-blue-700`
- [ ] Secondary 버튼: `bg-gray-600 hover:bg-gray-700`
- [ ] 텍스트: `text-gray-800` (제목), `text-gray-700` (본문), `text-gray-500` (보조)

### 간격
- [ ] 페이지 헤더 하단: `mb-6`
- [ ] 필터 영역 하단: `mb-6`
- [ ] 필터 요소 간격: `gap-4`
- [ ] 테이블 셀 패딩: `px-4 py-3` (본문), `px-4 py-2` (헤더)

### 둥근 모서리
- [ ] 버튼: `rounded-lg`
- [ ] 입력 필드: `rounded-lg`
- [ ] 배지: `rounded` (0.375rem)
- [ ] 컨테이너: `rounded-lg`

### 그림자
- [ ] 컨테이너: `shadow-sm`
- [ ] 페이지네이션: `shadow-sm`

8. 참고 사항

8.1 권한 관리 페이지 특수 기능

권한 관리 페이지는 다음과 같은 특수 기능을 포함합니다:

  1. 권한명 파싱: menu:{menu_id}.{permission_type} 형식 파싱
  2. 권한 타입 배지: V(조회), C(생성), U(수정), D(삭제), A(승인), E(내보내기), M(관리)
  3. 메뉴 태그: 회색 배지로 메뉴 ID 표시
  4. 역할/부서 배지: 여러 개 배지를 가로 나열 (flex flex-nowrap gap-1)

이러한 특수 기능은 다른 페이지에서 필요에 따라 적용하거나 생략할 수 있습니다.

8.2 성능 최적화

  • Eager Loading: 관계 데이터를 미리 로드하여 N+1 쿼리 방지
    $items = Model::with(['relation1', 'relation2'])->paginate(20);
    
  • 페이지네이션: 기본값 20개, 최대 500개까지 지원
  • HTMX: 부분 HTML만 교체하여 빠른 반응성 제공

8.3 접근성

  • 시맨틱 HTML: <table>, <thead>, <tbody> 사용
  • 버튼 레이블: 명확한 액션 설명
  • 키보드 네비게이션: 버튼과 링크에 포커스 가능

9. 예제 코드

9.1 최소 구현 예제

resources/views/products/index.blade.php

@extends('layouts.app')

@section('title', '제품 관리')

@section('content')
    <!-- 페이지 헤더 -->
    <div class="flex justify-between items-center mb-6">
        <h1 class="text-2xl font-bold text-gray-800">제품 관리</h1>
        <a href="{{ route('products.create') }}" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition">
            + 새 제품
        </a>
    </div>

    <!-- 필터 영역 -->
    <div class="bg-white rounded-lg shadow-sm p-4 mb-6">
        <form id="filterForm" class="flex gap-4">
            <div class="flex-1">
                <input type="text"
                       name="search"
                       placeholder="제품명으로 검색..."
                       class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
            </div>
            <button type="submit" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded-lg transition">
                검색
            </button>
        </form>
    </div>

    <!-- 테이블 영역 -->
    <div id="product-table"
         hx-get="/api/admin/products"
         hx-trigger="load, filterSubmit from:body"
         hx-include="#filterForm"
         hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
         class="bg-white rounded-lg shadow-sm overflow-hidden">
        <div class="flex justify-center items-center p-12">
            <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
        </div>
    </div>
@endsection

@push('scripts')
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script>
    document.getElementById('filterForm').addEventListener('submit', function(e) {
        e.preventDefault();
        htmx.trigger('#product-table', 'filterSubmit');
    });

    function confirmDelete(id, name) {
        if (confirm(`"${name}" 제품을 삭제하시겠습니까?`)) {
            fetch(`/api/admin/products/${id}`, {
                method: 'DELETE',
                headers: {
                    'X-CSRF-TOKEN': '{{ csrf_token() }}',
                    'Accept': 'application/json'
                }
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    htmx.trigger('#product-table', 'filterSubmit');
                    alert(data.message);
                } else {
                    alert(data.message);
                }
            })
            .catch(error => {
                alert('제품 삭제 중 오류가 발생했습니다.');
            });
        }
    }
</script>
@endpush

resources/views/products/partials/table.blade.php

<div class="overflow-x-auto">
    <table class="min-w-full divide-y divide-gray-200">
        <thead class="bg-gray-50">
            <tr>
                <th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">ID</th>
                <th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">제품명</th>
                <th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">생성일</th>
                <th class="px-4 py-2 text-right text-sm font-semibold text-gray-700 uppercase tracking-wider">액션</th>
            </tr>
        </thead>
        <tbody class="bg-white divide-y divide-gray-200">
            @forelse($products as $product)
            <tr>
                <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
                    {{ $product->id }}
                </td>
                <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
                    {{ $product->name }}
                </td>
                <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
                    {{ $product->created_at?->format('Y-m-d H:i') ?? '-' }}
                </td>
                <td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium">
                    <a href="{{ route('products.edit', $product->id) }}"
                       class="text-blue-600 hover:text-blue-900 mr-3">
                        수정
                    </a>
                    <button onclick="confirmDelete({{ $product->id }}, '{{ $product->name }}')"
                            class="text-red-600 hover:text-red-900">
                        삭제
                    </button>
                </td>
            </tr>
            @empty
            <tr>
                <td colspan="4" class="px-6 py-12 text-center text-gray-500">
                    등록된 제품이 없습니다.
                </td>
            </tr>
            @endforelse
        </tbody>
    </table>
</div>

@include('partials.pagination', [
    'paginator' => $products,
    'target' => '#product-table',
    'includeForm' => '#filterForm'
])

10. React 테이블 (Blade + Babel)

참조 파일: resources/views/barobill/etax/index.blade.php 작성일: 2026-02-03

Blade 템플릿 내에서 React(Babel)를 사용하는 경우, 테이블 컬럼 너비 설정 시 주의해야 할 사항이 있습니다.

10.1 colgroup을 사용한 컬럼 너비 지정

React에서 table-fixed 레이아웃과 함께 컬럼 너비를 정확하게 지정하려면 colgroup을 사용해야 합니다. Tailwind의 w-[] 클래스만으로는 정확한 너비 적용이 어려울 수 있습니다.

잘못된 예시 (Tailwind 클래스만 사용)

// ❌ 테이블 셀에 Tailwind 클래스만 적용 - 비율이 의도대로 안 될 수 있음
<table className="w-full text-sm table-fixed">
    <thead>
        <tr>
            <th className="w-[30%]">품목명</th>
            <th className="w-[60px]">수량</th>
            <th className="w-[100px]">단가</th>
        </tr>
    </thead>
</table>

올바른 예시 (colgroup 사용)

// ✅ colgroup으로 명시적 너비 지정
<table className="w-full text-sm" style=@{{tableLayout: 'fixed'}}>
    <colgroup>
        <col style=@{{width: '30%'}} />   {/* 품목명 - 가장 넓게 */}
        <col style=@{{width: '60px'}} />  {/* 수량 - 작게 고정 */}
        <col style=@{{width: '100px'}} /> {/* 단가 - 수량보다 넓게 */}
        <col style=@{{width: '12%'}} />   {/* 공급가액 */}
        <col style=@{{width: '10%'}} />   {/* 세액 */}
        <col style=@{{width: '12%'}} />   {/* 금액 */}
        <col style=@{{width: '70px'}} />  {/* 과세 (select) */}
        <col style=@{{width: '40px'}} />  {/* 삭제 버튼 */}
    </colgroup>
    <thead className="bg-stone-100 border-b border-stone-200">
        <tr>
            <th className="px-3 py-2.5 text-left font-medium text-stone-700">품목명</th>
            <th className="px-2 py-2.5 text-right font-medium text-stone-700">수량</th>
            <th className="px-2 py-2.5 text-right font-medium text-stone-700">단가</th>
            {/* ... */}
        </tr>
    </thead>
</table>

10.2 Blade 템플릿에서 React 스타일 객체 이스케이프

중요: Blade 템플릿(.blade.php)에서 React의 스타일 객체 {{ }}를 사용하면 Blade가 이를 PHP echo 구문으로 해석하여 에러가 발생합니다.

에러 발생 코드

// ❌ Blade가 {{ }}를 PHP 변수로 해석 → 에러 발생
<table style={{tableLayout: 'fixed'}}>
// Error: Unknown named parameter $tableLayout

해결 방법: @{{ }} 사용

// ✅ @를 붙여 Blade 이스케이프 처리
<table style=@{{tableLayout: 'fixed'}}>
<col style=@{{width: '30%'}} />

@{{ }}를 사용하면 Blade가 해당 구문을 처리하지 않고 그대로 {{ }}로 출력하여 React/Babel이 정상적으로 해석합니다.

10.3 입력 테이블 컬럼 비율 가이드

품목 입력 테이블의 권장 컬럼 비율:

컬럼 너비 설명
품목명 30% 텍스트 입력, 가장 넓게
수량 60px 작은 숫자 입력, 고정 너비
단가 100px 금액 입력, 수량보다 넓게
공급가액 12% 계산된 금액 표시
세액 10% 계산된 금액 표시
금액 12% 합계 금액 표시
과세유형 70px select 박스
삭제 40px 버튼

비율 설정 원칙

  1. 입력 필드는 내용에 맞는 적절한 너비 확보
  2. 수량은 보통 작은 숫자이므로 좁게 (60px)
  3. 단가/금액은 큰 숫자를 표시하므로 넉넉하게
  4. 품목명은 텍스트 입력이므로 가장 넓게 (%)
  5. 버튼/아이콘은 고정 픽셀 (px)

10.4 전체 예제 코드

// Blade 템플릿 내 React 코드 (@push('scripts') 내부)
<table className="w-full text-sm" style=@{{tableLayout: 'fixed'}}>
    <colgroup>
        <col style=@{{width: '30%'}} />
        <col style=@{{width: '60px'}} />
        <col style=@{{width: '100px'}} />
        <col style=@{{width: '12%'}} />
        <col style=@{{width: '10%'}} />
        <col style=@{{width: '12%'}} />
        <col style=@{{width: '70px'}} />
        <col style=@{{width: '40px'}} />
    </colgroup>
    <thead className="bg-stone-100 border-b border-stone-200">
        <tr>
            <th className="px-3 py-2.5 text-left font-medium text-stone-700">품목명</th>
            <th className="px-2 py-2.5 text-right font-medium text-stone-700">수량</th>
            <th className="px-2 py-2.5 text-right font-medium text-stone-700">단가</th>
            <th className="px-2 py-2.5 text-right font-medium text-stone-700">공급가액</th>
            <th className="px-2 py-2.5 text-right font-medium text-stone-700">세액</th>
            <th className="px-2 py-2.5 text-right font-medium text-stone-700">금액</th>
            <th className="px-1 py-2.5 text-center font-medium text-stone-700">과세</th>
            <th className="px-1 py-2.5 text-center font-medium text-stone-700"></th>
        </tr>
    </thead>
    <tbody className="divide-y divide-stone-100">
        {/* 데이터 행들 */}
    </tbody>
</table>

11. 문서 이력

버전 날짜 작성자 변경 내용
1.0 2025-11-25 Claude 초안 작성 (권한 관리 페이지 기반)
1.1 2026-02-03 Claude React 테이블 섹션 추가 (colgroup, Blade 이스케이프)

12. 문의

이 문서에 대한 문의사항이나 개선 제안은 프로젝트 관리자에게 연락하세요.