- colgroup을 사용한 명시적 컬럼 너비 지정 방법
- Blade에서 React 스타일 객체 이스케이프 (@{{ }})
- 입력 테이블 권장 컬럼 비율 가이드 (품목명 30%, 수량 60px 등)
- 전체 예제 코드 포함
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
26 KiB
26 KiB
MNG 테이블 레이아웃 표준
기준 페이지:
/permissions(권한 관리) 작성일: 2025-11-25 목적: mng 프로젝트의 모든 테이블 페이지에서 일관된 레이아웃과 UX를 제공
📋 목차
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 권한 관리 페이지 특수 기능
권한 관리 페이지는 다음과 같은 특수 기능을 포함합니다:
- 권한명 파싱:
menu:{menu_id}.{permission_type}형식 파싱 - 권한 타입 배지: V(조회), C(생성), U(수정), D(삭제), A(승인), E(내보내기), M(관리)
- 메뉴 태그: 회색 배지로 메뉴 ID 표시
- 역할/부서 배지: 여러 개 배지를 가로 나열 (
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 | 버튼 |
비율 설정 원칙
- 입력 필드는 내용에 맞는 적절한 너비 확보
- 수량은 보통 작은 숫자이므로 좁게 (60px)
- 단가/금액은 큰 숫자를 표시하므로 넉넉하게
- 품목명은 텍스트 입력이므로 가장 넓게 (%)
- 버튼/아이콘은 고정 픽셀 (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. 문의
이 문서에 대한 문의사항이나 개선 제안은 프로젝트 관리자에게 연락하세요.