품목기준 필드 관리 UI 개선
- 필드 목록에 상태(활성/잠금), 설정(옵션/속성/검증/조건) 컬럼 추가 - Row 클릭 시 아코디언 형태로 JSON 데이터를 Key-Value 테이블로 표시 - 상세보기/수정 모달에 JSON 필드 편집 기능 추가 - 시스템 필드 시딩 탭에서 row 클릭 시 필드 관리 탭으로 이동 및 필터링 - JSON 렌더링용 _key-value-table partial 추가
This commit is contained in:
@@ -31,7 +31,7 @@ class="tab-btn border-b-2 border-blue-500 text-blue-600 py-4 px-1 text-sm font-m
|
||||
</button>
|
||||
<button id="tab-custom" onclick="switchTab('custom')"
|
||||
class="tab-btn border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 py-4 px-1 text-sm font-medium">
|
||||
커스텀 필드 관리
|
||||
필드 관리
|
||||
</button>
|
||||
<button id="tab-errors" onclick="switchTab('errors')"
|
||||
class="tab-btn border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 py-4 px-1 text-sm font-medium flex items-center gap-2">
|
||||
@@ -73,16 +73,49 @@ class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
<!-- 필터 및 추가 버튼 -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
|
||||
<form id="customFilterForm" class="flex flex-wrap gap-4 items-end">
|
||||
<!-- 필드 유형 필터 (버튼 그룹) -->
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">필드 유형</label>
|
||||
<input type="hidden" name="field_category" id="field_category_input" value="">
|
||||
<div class="inline-flex rounded-lg border border-gray-300 overflow-hidden">
|
||||
<button type="button" onclick="setFieldCategory('')"
|
||||
class="field-category-btn px-3 py-2 text-sm font-medium bg-gray-800 text-white"
|
||||
data-value="">
|
||||
전체
|
||||
</button>
|
||||
<button type="button" onclick="setFieldCategory('system')"
|
||||
class="field-category-btn px-3 py-2 text-sm font-medium bg-white text-gray-700 hover:bg-gray-50 border-l border-gray-300"
|
||||
data-value="system">
|
||||
<span class="inline-flex items-center">
|
||||
<svg class="w-3 h-3 mr-1 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
시스템
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" onclick="setFieldCategory('custom')"
|
||||
class="field-category-btn px-3 py-2 text-sm font-medium bg-white text-gray-700 hover:bg-gray-50 border-l border-gray-300"
|
||||
data-value="custom">
|
||||
<span class="inline-flex items-center">
|
||||
<svg class="w-3 h-3 mr-1 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||
</svg>
|
||||
커스텀
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 소스 테이블 필터 -->
|
||||
<div class="w-48">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">소스 테이블</label>
|
||||
<select name="source_table" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<select name="source_table" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
|
||||
<option value="">전체</option>
|
||||
<option value="products">제품</option>
|
||||
<option value="materials">자재</option>
|
||||
<option value="product_components">BOM</option>
|
||||
<option value="material_inspections">자재검수</option>
|
||||
<option value="material_receipts">자재입고</option>
|
||||
<option value="products">products</option>
|
||||
<option value="materials">materials</option>
|
||||
<option value="product_components">product_components</option>
|
||||
<option value="material_inspections">material_inspections</option>
|
||||
<option value="material_receipts">material_receipts</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -129,9 +162,9 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm fon
|
||||
hx-include="#customFilterForm"
|
||||
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 text-gray-500">
|
||||
검색 조건을 설정하고 검색 버튼을 클릭하세요.
|
||||
<!-- 로딩 스피너 (탭 전환 시 자동 로드) -->
|
||||
<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>
|
||||
</div>
|
||||
@@ -170,13 +203,13 @@ class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
<!-- 소스 테이블 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">소스 테이블 <span class="text-red-500">*</span></label>
|
||||
<select name="source_table" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<select name="source_table" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono">
|
||||
<option value="">선택하세요</option>
|
||||
<option value="products">제품</option>
|
||||
<option value="materials">자재</option>
|
||||
<option value="product_components">BOM</option>
|
||||
<option value="material_inspections">자재검수</option>
|
||||
<option value="material_receipts">자재입고</option>
|
||||
<option value="products">products</option>
|
||||
<option value="materials">materials</option>
|
||||
<option value="product_components">product_components</option>
|
||||
<option value="material_inspections">material_inspections</option>
|
||||
<option value="material_receipts">material_receipts</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -312,12 +345,198 @@ class="px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 필드 상세보기 모달 -->
|
||||
<div id="detailModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
|
||||
<div class="flex items-center justify-center min-h-screen px-4">
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50" onclick="closeDetailModal()"></div>
|
||||
<div class="relative bg-white rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div class="sticky top-0 bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center">
|
||||
<h3 class="text-lg font-bold text-gray-800">필드 상세정보</h3>
|
||||
<button onclick="closeDetailModal()" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<!-- 기본 정보 -->
|
||||
<div class="mb-6">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
기본 정보
|
||||
</h4>
|
||||
<div class="grid grid-cols-2 gap-4 bg-gray-50 rounded-lg p-4">
|
||||
<div>
|
||||
<span class="text-xs text-gray-500">필드 키</span>
|
||||
<p id="detail_field_key" class="text-sm font-mono font-medium mt-1">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-xs text-gray-500">필드명</span>
|
||||
<p id="detail_field_name" class="text-sm font-medium mt-1">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-xs text-gray-500">소스 테이블</span>
|
||||
<p id="detail_source_table" class="text-sm font-mono mt-1">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-xs text-gray-500">필드 타입</span>
|
||||
<p id="detail_field_type" class="text-sm mt-1">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-xs text-gray-500">저장 방식</span>
|
||||
<p id="detail_storage_type" class="text-sm mt-1">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-xs text-gray-500">카테고리</span>
|
||||
<p id="detail_category" class="text-sm mt-1">-</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 상태 정보 -->
|
||||
<div class="mb-6">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
상태
|
||||
</h4>
|
||||
<div class="flex flex-wrap gap-4 bg-gray-50 rounded-lg p-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-gray-500">필수:</span>
|
||||
<span id="detail_is_required" class="text-sm">-</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-gray-500">활성:</span>
|
||||
<span id="detail_is_active" class="text-sm">-</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-gray-500">잠금:</span>
|
||||
<span id="detail_is_locked" class="text-sm">-</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-gray-500">공통:</span>
|
||||
<span id="detail_is_common" class="text-sm">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 기본값/Placeholder -->
|
||||
<div class="mb-6">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
입력 설정
|
||||
</h4>
|
||||
<div class="grid grid-cols-2 gap-4 bg-gray-50 rounded-lg p-4">
|
||||
<div>
|
||||
<span class="text-xs text-gray-500">기본값</span>
|
||||
<p id="detail_default_value" class="text-sm mt-1">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-xs text-gray-500">Placeholder</span>
|
||||
<p id="detail_placeholder" class="text-sm mt-1">-</p>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<span class="text-xs text-gray-500">설명</span>
|
||||
<p id="detail_description" class="text-sm mt-1">-</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options (JSON) -->
|
||||
<div id="detail_options_section" class="mb-6 hidden">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 mr-1 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
</svg>
|
||||
옵션 (Options)
|
||||
<span class="ml-2 text-xs text-gray-400">드롭다운 선택 항목 등</span>
|
||||
</h4>
|
||||
<pre id="detail_options" class="bg-gray-900 text-green-400 p-4 rounded-lg text-xs overflow-x-auto max-h-48"></pre>
|
||||
</div>
|
||||
|
||||
<!-- Properties (JSON) -->
|
||||
<div id="detail_properties_section" class="mb-6 hidden">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 mr-1 text-cyan-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
속성 (Properties)
|
||||
<span class="ml-2 text-xs text-gray-400">멀티컬럼, 추가 설정 등</span>
|
||||
</h4>
|
||||
<pre id="detail_properties" class="bg-gray-900 text-cyan-400 p-4 rounded-lg text-xs overflow-x-auto max-h-48"></pre>
|
||||
</div>
|
||||
|
||||
<!-- Validation Rules (JSON) -->
|
||||
<div id="detail_validation_section" class="mb-6 hidden">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 mr-1 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
유효성 검사 (Validation Rules)
|
||||
</h4>
|
||||
<pre id="detail_validation_rules" class="bg-gray-900 text-yellow-400 p-4 rounded-lg text-xs overflow-x-auto max-h-48"></pre>
|
||||
</div>
|
||||
|
||||
<!-- Display Condition (JSON) -->
|
||||
<div id="detail_display_condition_section" class="mb-6 hidden">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 mr-1 text-pink-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
표시 조건 (Display Condition)
|
||||
</h4>
|
||||
<pre id="detail_display_condition" class="bg-gray-900 text-pink-400 p-4 rounded-lg text-xs overflow-x-auto max-h-48"></pre>
|
||||
</div>
|
||||
|
||||
<!-- 시간 정보 -->
|
||||
<div class="mb-6">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
시간 정보
|
||||
</h4>
|
||||
<div class="grid grid-cols-2 gap-4 bg-gray-50 rounded-lg p-4 text-xs">
|
||||
<div>
|
||||
<span class="text-gray-500">생성일:</span>
|
||||
<span id="detail_created_at" class="ml-2">-</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-500">수정일:</span>
|
||||
<span id="detail_updated_at" class="ml-2">-</span>
|
||||
</div>
|
||||
<div id="detail_locked_info" class="col-span-2 hidden">
|
||||
<span class="text-gray-500">잠금 정보:</span>
|
||||
<span id="detail_locked_at" class="ml-2 text-orange-600">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sticky bottom-0 bg-white border-t border-gray-200 px-6 py-4 flex justify-end gap-2">
|
||||
<button type="button" onclick="closeDetailModal()"
|
||||
class="px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors">
|
||||
닫기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 커스텀 필드 수정 모달 -->
|
||||
<div id="editModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
|
||||
<div class="flex items-center justify-center min-h-screen px-4">
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50" onclick="closeEditModal()"></div>
|
||||
<div class="relative bg-white rounded-lg shadow-xl max-w-lg w-full p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="relative bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div class="sticky top-0 bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center">
|
||||
<h3 class="text-lg font-bold text-gray-800">필드 수정</h3>
|
||||
<button onclick="closeEditModal()" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -326,71 +545,171 @@ class="px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="editForm" onsubmit="submitEditForm(event)">
|
||||
<form id="editForm" onsubmit="submitEditForm(event)" class="p-6">
|
||||
<input type="hidden" name="id" id="edit_id">
|
||||
<input type="hidden" name="storage_type" id="edit_storage_type">
|
||||
|
||||
<!-- 소스 테이블 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">소스 테이블</label>
|
||||
<select name="source_table" id="edit_source_table" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100">
|
||||
<option value="">미지정</option>
|
||||
<option value="products">제품</option>
|
||||
<option value="materials">자재</option>
|
||||
<option value="product_components">BOM</option>
|
||||
<option value="material_inspections">자재검수</option>
|
||||
<option value="material_receipts">자재입고</option>
|
||||
</select>
|
||||
<p id="edit_source_table_hint" class="text-xs text-gray-500 mt-1 hidden">시스템 필드는 소스 테이블을 변경할 수 없습니다.</p>
|
||||
<!-- 기본 정보 섹션 -->
|
||||
<div class="mb-6">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3 pb-2 border-b">기본 정보</h4>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- 소스 테이블 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">소스 테이블</label>
|
||||
<select name="source_table" id="edit_source_table" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 font-mono">
|
||||
<option value="">미지정</option>
|
||||
<option value="products">products</option>
|
||||
<option value="materials">materials</option>
|
||||
<option value="product_components">product_components</option>
|
||||
<option value="material_inspections">material_inspections</option>
|
||||
<option value="material_receipts">material_receipts</option>
|
||||
</select>
|
||||
<p id="edit_source_table_hint" class="text-xs text-gray-500 mt-1 hidden">시스템 필드는 변경 불가</p>
|
||||
</div>
|
||||
|
||||
<!-- 필드 키 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">필드 키</label>
|
||||
<input type="text" name="field_key" id="edit_field_key"
|
||||
placeholder="예: custom_field_1"
|
||||
pattern="^[a-zA-Z0-9_]+$"
|
||||
maxlength="100"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 font-mono">
|
||||
<p id="edit_field_key_hint" class="text-xs text-gray-500 mt-1 hidden">시스템 필드는 변경 불가</p>
|
||||
<p id="edit_field_key_policy" class="text-xs text-gray-500 mt-1">영문, 숫자, _ (최대 100자)</p>
|
||||
</div>
|
||||
|
||||
<!-- 필드명 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">필드명 <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="field_name" id="edit_field_name" required
|
||||
placeholder="필드 표시명"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<!-- 필드 타입 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">필드 타입 <span class="text-red-500">*</span></label>
|
||||
<select name="field_type" id="edit_field_type" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="textbox">텍스트</option>
|
||||
<option value="number">숫자</option>
|
||||
<option value="dropdown">드롭다운</option>
|
||||
<option value="checkbox">체크박스</option>
|
||||
<option value="date">날짜</option>
|
||||
<option value="textarea">텍스트영역</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 기본값 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">기본값</label>
|
||||
<input type="text" name="default_value" id="edit_default_value"
|
||||
placeholder="기본값 (선택)"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<!-- Placeholder -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Placeholder</label>
|
||||
<input type="text" name="placeholder" id="edit_placeholder"
|
||||
placeholder="입력 안내 문구"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<!-- 설명 -->
|
||||
<div class="col-span-2">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">설명</label>
|
||||
<textarea name="description" id="edit_description" rows="2"
|
||||
placeholder="필드 설명 (선택)"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 필드 키 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">필드 키</label>
|
||||
<input type="text" name="field_key" id="edit_field_key"
|
||||
placeholder="예: custom_field_1, 96_item_name"
|
||||
pattern="^[a-zA-Z0-9_]+$"
|
||||
maxlength="100"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100">
|
||||
<p id="edit_field_key_hint" class="text-xs text-gray-500 mt-1 hidden">시스템 필드는 필드 키를 변경할 수 없습니다.</p>
|
||||
<p id="edit_field_key_policy" class="text-xs text-gray-500 mt-1">영문, 숫자, 밑줄(_)만 사용 가능 (최대 100자)</p>
|
||||
<!-- 상태 설정 섹션 -->
|
||||
<div class="mb-6">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3 pb-2 border-b">상태 설정</h4>
|
||||
<div class="flex flex-wrap gap-6">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_required" id="edit_is_required" value="1" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||
<span class="ml-2 text-sm text-gray-700">필수 입력</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_active" id="edit_is_active" value="1" class="rounded border-gray-300 text-green-600 focus:ring-green-500">
|
||||
<span class="ml-2 text-sm text-gray-700">활성화</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_locked" id="edit_is_locked" value="1" class="rounded border-gray-300 text-orange-600 focus:ring-orange-500">
|
||||
<span class="ml-2 text-sm text-gray-700">잠금</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 필드명 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">필드명 <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="field_name" id="edit_field_name" required
|
||||
placeholder="필드 표시명"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
<!-- JSON 필드 섹션 -->
|
||||
<div class="mb-6">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3 pb-2 border-b flex items-center justify-between">
|
||||
<span>고급 설정 (JSON)</span>
|
||||
<button type="button" onclick="toggleJsonHelp()" class="text-xs text-blue-600 hover:text-blue-800">
|
||||
입력 도움말
|
||||
</button>
|
||||
</h4>
|
||||
|
||||
<!-- 필드 타입 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">필드 타입 <span class="text-red-500">*</span></label>
|
||||
<select name="field_type" id="edit_field_type" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="textbox">텍스트</option>
|
||||
<option value="number">숫자</option>
|
||||
<option value="dropdown">드롭다운</option>
|
||||
<option value="checkbox">체크박스</option>
|
||||
<option value="date">날짜</option>
|
||||
<option value="textarea">텍스트영역</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- JSON 도움말 -->
|
||||
<div id="jsonHelp" class="hidden mb-4 p-3 bg-blue-50 rounded-lg text-xs text-blue-800">
|
||||
<p class="font-medium mb-2">JSON 입력 예시:</p>
|
||||
<div class="space-y-2 font-mono">
|
||||
<p><strong>옵션 (드롭다운용):</strong> [{"label": "옵션1", "value": "opt1"}, {"label": "옵션2", "value": "opt2"}]</p>
|
||||
<p><strong>속성 (멀티컬럼):</strong> {"multiColumn": true, "columnCount": 2, "columnNames": ["가로", "세로"]}</p>
|
||||
<p><strong>유효성:</strong> {"minLength": 1, "maxLength": 100, "pattern": "^[a-zA-Z]+$"}</p>
|
||||
<p><strong>표시조건:</strong> {"targetType": "field", "fieldConditions": [{"fieldKey": "type", "expectedValue": "A"}]}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 필수 여부 -->
|
||||
<div class="mb-4">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_required" id="edit_is_required" value="1" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||
<span class="ml-2 text-sm text-gray-700">필수 입력</span>
|
||||
</label>
|
||||
</div>
|
||||
<!-- Options -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1 flex items-center">
|
||||
<span class="w-2 h-2 bg-purple-500 rounded-full mr-2"></span>
|
||||
옵션 (Options)
|
||||
<span class="ml-2 text-xs text-gray-400">드롭다운 선택 항목</span>
|
||||
</label>
|
||||
<textarea name="options" id="edit_options" rows="3"
|
||||
placeholder='[{"label": "옵션1", "value": "opt1"}]'
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-purple-500 font-mono"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 기본값 -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">기본값</label>
|
||||
<input type="text" name="default_value" id="edit_default_value"
|
||||
placeholder="기본값 (선택사항)"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<!-- Properties -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1 flex items-center">
|
||||
<span class="w-2 h-2 bg-cyan-500 rounded-full mr-2"></span>
|
||||
속성 (Properties)
|
||||
<span class="ml-2 text-xs text-gray-400">멀티컬럼, 추가 설정</span>
|
||||
</label>
|
||||
<textarea name="properties" id="edit_properties" rows="3"
|
||||
placeholder='{"multiColumn": true, "columnCount": 2}'
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500 font-mono"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Validation Rules -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1 flex items-center">
|
||||
<span class="w-2 h-2 bg-yellow-500 rounded-full mr-2"></span>
|
||||
유효성 검사 (Validation Rules)
|
||||
</label>
|
||||
<textarea name="validation_rules" id="edit_validation_rules" rows="3"
|
||||
placeholder='{"minLength": 1, "maxLength": 100}'
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-yellow-500 font-mono"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Display Condition -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1 flex items-center">
|
||||
<span class="w-2 h-2 bg-pink-500 rounded-full mr-2"></span>
|
||||
표시 조건 (Display Condition)
|
||||
</label>
|
||||
<textarea name="display_condition" id="edit_display_condition" rows="3"
|
||||
placeholder='{"targetType": "field", "fieldConditions": [...]}'
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-pink-500 font-mono"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 필드 정보 표시 -->
|
||||
@@ -406,7 +725,7 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-
|
||||
</div>
|
||||
|
||||
<!-- 버튼 -->
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<div class="flex justify-end gap-2 pt-4 border-t">
|
||||
<button type="button" onclick="closeEditModal()"
|
||||
class="px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors">
|
||||
취소
|
||||
@@ -460,6 +779,21 @@ function refreshSeeding() {
|
||||
htmx.trigger('#seeding-status', 'seedingRefresh');
|
||||
}
|
||||
|
||||
// 시딩 탭에서 필드 관리 탭으로 이동 (source_table 필터링)
|
||||
window.goToFieldManagement = function(sourceTable) {
|
||||
// 1. 소스 테이블 필터 설정
|
||||
const sourceTableSelect = document.querySelector('#customFilterForm select[name="source_table"]');
|
||||
if (sourceTableSelect) {
|
||||
sourceTableSelect.value = sourceTable;
|
||||
}
|
||||
|
||||
// 2. 필드 유형을 '시스템'으로 설정
|
||||
setFieldCategory('system');
|
||||
|
||||
// 3. 필드 관리 탭으로 전환 (데이터 로드 포함)
|
||||
switchTab('custom');
|
||||
};
|
||||
|
||||
// 오류 처리 공통 함수
|
||||
function handleSeedingResponse(data) {
|
||||
if (data.success) {
|
||||
@@ -700,20 +1034,122 @@ function copyReportToClipboard() {
|
||||
showToast('보고서가 클립보드에 복사되었습니다.', 'success');
|
||||
}
|
||||
|
||||
// 상세보기 모달 열기
|
||||
window.openDetailModal = function(field) {
|
||||
const modal = document.getElementById('detailModal');
|
||||
const typeLabels = {
|
||||
'textbox': '텍스트',
|
||||
'number': '숫자',
|
||||
'dropdown': '드롭다운',
|
||||
'checkbox': '체크박스',
|
||||
'date': '날짜',
|
||||
'textarea': '텍스트영역'
|
||||
};
|
||||
|
||||
// 기본 정보
|
||||
document.getElementById('detail_field_key').textContent = field.field_key || '-';
|
||||
document.getElementById('detail_field_name').textContent = field.field_name || '-';
|
||||
document.getElementById('detail_source_table').textContent = field.source_table || '-';
|
||||
document.getElementById('detail_field_type').textContent = typeLabels[field.field_type] || field.field_type || '-';
|
||||
document.getElementById('detail_storage_type').textContent = field.storage_type === 'column' ? '시스템 (컬럼)' : '커스텀 (JSON)';
|
||||
document.getElementById('detail_category').textContent = field.category || '-';
|
||||
|
||||
// 상태
|
||||
document.getElementById('detail_is_required').innerHTML = field.is_required ? '<span class="text-green-600 font-medium">예</span>' : '<span class="text-gray-400">아니오</span>';
|
||||
document.getElementById('detail_is_active').innerHTML = field.is_active ? '<span class="text-green-600 font-medium">활성</span>' : '<span class="text-gray-400">비활성</span>';
|
||||
document.getElementById('detail_is_locked').innerHTML = field.is_locked ? '<span class="text-orange-600 font-medium">잠금</span>' : '<span class="text-gray-400">미잠금</span>';
|
||||
document.getElementById('detail_is_common').innerHTML = field.is_common ? '<span class="text-blue-600 font-medium">공통</span>' : '<span class="text-gray-400">개별</span>';
|
||||
|
||||
// 입력 설정
|
||||
document.getElementById('detail_default_value').textContent = field.default_value || '-';
|
||||
document.getElementById('detail_placeholder').textContent = field.placeholder || '-';
|
||||
document.getElementById('detail_description').textContent = field.description || '-';
|
||||
|
||||
// JSON 필드들
|
||||
const showJsonSection = (sectionId, elementId, data) => {
|
||||
const section = document.getElementById(sectionId);
|
||||
const element = document.getElementById(elementId);
|
||||
if (data) {
|
||||
try {
|
||||
const parsed = typeof data === 'string' ? JSON.parse(data) : data;
|
||||
element.textContent = JSON.stringify(parsed, null, 2);
|
||||
section.classList.remove('hidden');
|
||||
} catch (e) {
|
||||
element.textContent = data;
|
||||
section.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
section.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
showJsonSection('detail_options_section', 'detail_options', field.options);
|
||||
showJsonSection('detail_properties_section', 'detail_properties', field.properties);
|
||||
showJsonSection('detail_validation_section', 'detail_validation_rules', field.validation_rules);
|
||||
showJsonSection('detail_display_condition_section', 'detail_display_condition', field.display_condition);
|
||||
|
||||
// 시간 정보
|
||||
document.getElementById('detail_created_at').textContent = field.created_at ? new Date(field.created_at).toLocaleString('ko-KR') : '-';
|
||||
document.getElementById('detail_updated_at').textContent = field.updated_at ? new Date(field.updated_at).toLocaleString('ko-KR') : '-';
|
||||
|
||||
// 잠금 정보
|
||||
const lockedInfo = document.getElementById('detail_locked_info');
|
||||
if (field.is_locked && field.locked_at) {
|
||||
document.getElementById('detail_locked_at').textContent = `${new Date(field.locked_at).toLocaleString('ko-KR')} (ID: ${field.locked_by || '-'})`;
|
||||
lockedInfo.classList.remove('hidden');
|
||||
} else {
|
||||
lockedInfo.classList.add('hidden');
|
||||
}
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
};
|
||||
|
||||
// 상세보기 모달 닫기
|
||||
function closeDetailModal() {
|
||||
document.getElementById('detailModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// JSON 도움말 토글
|
||||
function toggleJsonHelp() {
|
||||
document.getElementById('jsonHelp').classList.toggle('hidden');
|
||||
}
|
||||
|
||||
// 수정 모달 열기
|
||||
window.openEditModal = function(field) {
|
||||
const modal = document.getElementById('editModal');
|
||||
const isSystemField = field.storage_type === 'column';
|
||||
|
||||
// 폼 필드 채우기
|
||||
// 기본 필드 채우기
|
||||
document.getElementById('edit_id').value = field.id;
|
||||
document.getElementById('edit_storage_type').value = field.storage_type;
|
||||
document.getElementById('edit_source_table').value = field.source_table || '';
|
||||
document.getElementById('edit_field_key').value = field.field_key || '';
|
||||
document.getElementById('edit_field_name').value = field.field_name;
|
||||
document.getElementById('edit_field_type').value = field.field_type;
|
||||
document.getElementById('edit_is_required').checked = field.is_required;
|
||||
document.getElementById('edit_default_value').value = field.default_value || '';
|
||||
document.getElementById('edit_placeholder').value = field.placeholder || '';
|
||||
document.getElementById('edit_description').value = field.description || '';
|
||||
|
||||
// 체크박스
|
||||
document.getElementById('edit_is_required').checked = field.is_required;
|
||||
document.getElementById('edit_is_active').checked = field.is_active;
|
||||
document.getElementById('edit_is_locked').checked = field.is_locked;
|
||||
|
||||
// JSON 필드 (문자열로 변환)
|
||||
const formatJson = (data) => {
|
||||
if (!data) return '';
|
||||
try {
|
||||
const parsed = typeof data === 'string' ? JSON.parse(data) : data;
|
||||
return JSON.stringify(parsed, null, 2);
|
||||
} catch (e) {
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('edit_options').value = formatJson(field.options);
|
||||
document.getElementById('edit_properties').value = formatJson(field.properties);
|
||||
document.getElementById('edit_validation_rules').value = formatJson(field.validation_rules);
|
||||
document.getElementById('edit_display_condition').value = formatJson(field.display_condition);
|
||||
|
||||
// 시스템 필드인 경우 source_table, field_key 비활성화
|
||||
document.getElementById('edit_source_table').disabled = isSystemField;
|
||||
@@ -728,6 +1164,9 @@ function copyReportToClipboard() {
|
||||
document.getElementById('edit_created_at').textContent =
|
||||
field.created_at ? new Date(field.created_at).toLocaleString('ko-KR') : '-';
|
||||
|
||||
// JSON 도움말 숨기기
|
||||
document.getElementById('jsonHelp').classList.add('hidden');
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
};
|
||||
|
||||
@@ -737,6 +1176,17 @@ function closeEditModal() {
|
||||
document.getElementById('editForm').reset();
|
||||
}
|
||||
|
||||
// JSON 파싱 헬퍼 (빈 문자열이면 null 반환)
|
||||
function parseJsonField(value) {
|
||||
if (!value || value.trim() === '') return null;
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
showToast('JSON 형식이 올바르지 않습니다: ' + e.message, 'error');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// 수정 폼 제출
|
||||
function submitEditForm(e) {
|
||||
e.preventDefault();
|
||||
@@ -746,11 +1196,30 @@ function submitEditForm(e) {
|
||||
const storageType = formData.get('storage_type');
|
||||
const isSystemField = storageType === 'column';
|
||||
|
||||
// JSON 필드 파싱 시도
|
||||
let options, properties, validationRules, displayCondition;
|
||||
try {
|
||||
options = parseJsonField(formData.get('options'));
|
||||
properties = parseJsonField(formData.get('properties'));
|
||||
validationRules = parseJsonField(formData.get('validation_rules'));
|
||||
displayCondition = parseJsonField(formData.get('display_condition'));
|
||||
} catch (e) {
|
||||
return; // JSON 파싱 에러 시 중단
|
||||
}
|
||||
|
||||
const data = {
|
||||
field_name: formData.get('field_name'),
|
||||
field_type: formData.get('field_type'),
|
||||
is_required: formData.get('is_required') === '1',
|
||||
default_value: formData.get('default_value') || null
|
||||
is_active: formData.get('is_active') === '1',
|
||||
is_locked: formData.get('is_locked') === '1',
|
||||
default_value: formData.get('default_value') || null,
|
||||
placeholder: formData.get('placeholder') || null,
|
||||
description: formData.get('description') || null,
|
||||
options: options,
|
||||
properties: properties,
|
||||
validation_rules: validationRules,
|
||||
display_condition: displayCondition
|
||||
};
|
||||
|
||||
// 커스텀 필드인 경우에만 source_table, field_key 포함
|
||||
@@ -780,5 +1249,25 @@ function submitEditForm(e) {
|
||||
})
|
||||
.catch(err => showToast('오류가 발생했습니다.', 'error'));
|
||||
}
|
||||
|
||||
// 필드 유형 버튼 그룹 선택
|
||||
function setFieldCategory(value) {
|
||||
// hidden input 값 업데이트
|
||||
document.getElementById('field_category_input').value = value;
|
||||
|
||||
// 버튼 스타일 업데이트
|
||||
document.querySelectorAll('.field-category-btn').forEach(btn => {
|
||||
if (btn.dataset.value === value) {
|
||||
btn.classList.remove('bg-white', 'text-gray-700', 'hover:bg-gray-50');
|
||||
btn.classList.add('bg-gray-800', 'text-white');
|
||||
} else {
|
||||
btn.classList.remove('bg-gray-800', 'text-white');
|
||||
btn.classList.add('bg-white', 'text-gray-700', 'hover:bg-gray-50');
|
||||
}
|
||||
});
|
||||
|
||||
// 즉시 검색 실행
|
||||
htmx.trigger('#custom-fields', 'customRefresh');
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
{{-- Key-Value 테이블 표시용 Partial --}}
|
||||
@php
|
||||
/**
|
||||
* JSON 데이터를 Key-Value 테이블로 렌더링
|
||||
* 중첩 배열/객체는 재귀적으로 표시
|
||||
*/
|
||||
if (!function_exists('renderJsonValue')) {
|
||||
function renderJsonValue($value, $depth = 0) {
|
||||
if (is_null($value)) {
|
||||
return '<span class="text-gray-400 italic">null</span>';
|
||||
}
|
||||
if (is_bool($value)) {
|
||||
return $value
|
||||
? '<span class="text-green-600 font-medium">true</span>'
|
||||
: '<span class="text-red-500 font-medium">false</span>';
|
||||
}
|
||||
if (is_numeric($value)) {
|
||||
return '<span class="font-mono text-blue-600">' . e($value) . '</span>';
|
||||
}
|
||||
if (is_string($value)) {
|
||||
return '<span class="text-gray-700">' . e($value) . '</span>';
|
||||
}
|
||||
if (is_array($value)) {
|
||||
// 순차 배열인지 연관 배열인지 확인
|
||||
if (empty($value)) {
|
||||
return '<span class="text-gray-400 italic">[]</span>';
|
||||
}
|
||||
if (array_keys($value) === range(0, count($value) - 1)) {
|
||||
// 순차 배열: 인라인으로 표시
|
||||
if (count($value) <= 5 && !array_filter($value, 'is_array')) {
|
||||
// 단순 배열이면 인라인 표시
|
||||
$items = array_map(fn($v) => is_string($v) ? e($v) : e(json_encode($v, JSON_UNESCAPED_UNICODE)), $value);
|
||||
return '<span class="text-gray-600">[' . implode(', ', $items) . ']</span>';
|
||||
}
|
||||
// 복잡한 배열은 리스트로 표시
|
||||
$html = '<div class="ml-2 border-l-2 border-gray-200 pl-2 mt-1 space-y-1">';
|
||||
foreach ($value as $idx => $item) {
|
||||
$html .= '<div class="flex items-start gap-1">';
|
||||
$html .= '<span class="text-gray-400 text-xs">[' . $idx . ']</span>';
|
||||
$html .= '<div>' . renderJsonValue($item, $depth + 1) . '</div>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
} else {
|
||||
// 연관 배열: 중첩 테이블로 표시
|
||||
$html = '<div class="ml-2 border-l-2 border-gray-200 pl-2 mt-1">';
|
||||
foreach ($value as $k => $v) {
|
||||
$html .= '<div class="flex items-start gap-2 py-0.5">';
|
||||
$html .= '<span class="text-gray-500 font-medium text-xs shrink-0">' . e($k) . ':</span>';
|
||||
$html .= '<div class="text-xs">' . renderJsonValue($v, $depth + 1) . '</div>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
return '<span class="text-gray-400">' . e(gettype($value)) . '</span>';
|
||||
}
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if(is_array($data) && !empty($data))
|
||||
<table class="w-full text-xs">
|
||||
<thead>
|
||||
<tr class="border-b">
|
||||
<th class="text-left py-1 text-gray-500 font-medium w-1/3">Key</th>
|
||||
<th class="text-left py-1 text-gray-500 font-medium">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($data as $key => $value)
|
||||
<tr class="border-b border-gray-100 last:border-0 align-top">
|
||||
<td class="py-1.5 font-mono text-gray-600 font-medium">{{ $key }}</td>
|
||||
<td class="py-1.5">{!! renderJsonValue($value) !!}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<span class="text-xs text-gray-400">데이터 없음</span>
|
||||
@endif
|
||||
@@ -14,102 +14,330 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
|
||||
</svg>
|
||||
</div>
|
||||
<p>커스텀 필드가 없습니다.</p>
|
||||
<p class="text-xs mt-1">새 커스텀 필드를 추가해보세요.</p>
|
||||
<p>등록된 필드가 없습니다.</p>
|
||||
<p class="text-xs mt-1">시스템 필드 시딩 또는 커스텀 필드 추가를 해보세요.</p>
|
||||
</div>
|
||||
@else
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50 border-b border-gray-200">
|
||||
<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>
|
||||
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">타입</th>
|
||||
<th class="px-6 py-3 text-center 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-right text-xs font-medium text-gray-500 uppercase tracking-wider">액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@foreach($fields as $field)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4">
|
||||
@if(empty($field->source_table))
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600">
|
||||
미지정
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50 border-b border-gray-200">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-8"></th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">상태</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">유형</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">소스 테이블</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">필드 키</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">필드명</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">타입</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">필수</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">설정</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@foreach($fields as $field)
|
||||
@php
|
||||
$isSystemField = $field->is_common || $field->storage_type === 'column';
|
||||
$hasOptions = !empty($field->options);
|
||||
$hasProperties = !empty($field->properties);
|
||||
$hasValidation = !empty($field->validation_rules);
|
||||
$hasDisplayCondition = !empty($field->display_condition);
|
||||
$hasAnyJson = $hasOptions || $hasProperties || $hasValidation || $hasDisplayCondition;
|
||||
// 모델에서 이미 캐스팅된 경우 배열, 아니면 문자열
|
||||
$optionsData = $hasOptions ? (is_array($field->options) ? $field->options : json_decode($field->options, true)) : null;
|
||||
$propertiesData = $hasProperties ? (is_array($field->properties) ? $field->properties : json_decode($field->properties, true)) : null;
|
||||
$validationData = $hasValidation ? (is_array($field->validation_rules) ? $field->validation_rules : json_decode($field->validation_rules, true)) : null;
|
||||
$displayConditionData = $hasDisplayCondition ? (is_array($field->display_condition) ? $field->display_condition : json_decode($field->display_condition, true)) : null;
|
||||
@endphp
|
||||
<!-- 메인 Row -->
|
||||
<tr class="hover:bg-gray-50 {{ $isSystemField ? 'bg-blue-50/30' : '' }} {{ !$field->is_active ? 'opacity-50' : '' }} {{ $hasAnyJson ? 'cursor-pointer' : '' }}"
|
||||
@if($hasAnyJson) onclick="toggleAccordion({{ $field->id }})" @endif>
|
||||
<!-- 펼침 아이콘 -->
|
||||
<td class="px-2 py-3 text-center">
|
||||
@if($hasAnyJson)
|
||||
<svg id="chevron-{{ $field->id }}" class="w-4 h-4 text-gray-400 transform transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
@endif
|
||||
</td>
|
||||
<!-- 상태 (활성/비활성, 잠금) -->
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-center gap-1">
|
||||
@if($field->is_active)
|
||||
<span class="w-2 h-2 bg-green-500 rounded-full" title="활성"></span>
|
||||
@else
|
||||
<span class="w-2 h-2 bg-gray-400 rounded-full" title="비활성"></span>
|
||||
@endif
|
||||
@if($field->is_locked)
|
||||
<svg class="w-4 h-4 text-orange-500" fill="currentColor" viewBox="0 0 20 20" title="잠금됨">
|
||||
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<!-- 필드 유형 (시스템/커스텀) -->
|
||||
<td class="px-4 py-3">
|
||||
@if($isSystemField)
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
시스템
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||
커스텀
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<!-- 소스 테이블 -->
|
||||
<td class="px-4 py-3">
|
||||
@if(empty($field->source_table))
|
||||
<span class="text-gray-400 text-xs">미지정</span>
|
||||
@else
|
||||
<code class="text-xs font-mono text-gray-700 bg-gray-100 px-1.5 py-0.5 rounded">{{ $field->source_table }}</code>
|
||||
@endif
|
||||
</td>
|
||||
<!-- 필드 키 -->
|
||||
<td class="px-4 py-3">
|
||||
@if(empty($field->field_key))
|
||||
<span class="text-gray-400 text-xs">미지정</span>
|
||||
@else
|
||||
<code class="text-xs font-mono text-gray-700 bg-gray-100 px-1.5 py-0.5 rounded">{{ $field->field_key }}</code>
|
||||
@endif
|
||||
</td>
|
||||
<!-- 필드명 -->
|
||||
<td class="px-4 py-3">
|
||||
<div class="font-medium text-gray-900 text-sm">
|
||||
{{ $field->field_name }}
|
||||
@if($field->is_required)
|
||||
<span class="text-red-500">*</span>
|
||||
@endif
|
||||
</div>
|
||||
@if($field->description)
|
||||
<div class="text-xs text-gray-500 truncate max-w-[150px]" title="{{ $field->description }}">
|
||||
{{ $field->description }}
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
<!-- 타입 -->
|
||||
<td class="px-4 py-3 text-center">
|
||||
@php
|
||||
$typeLabels = [
|
||||
'textbox' => ['label' => '텍스트', 'color' => 'gray'],
|
||||
'number' => ['label' => '숫자', 'color' => 'blue'],
|
||||
'dropdown' => ['label' => '드롭다운', 'color' => 'purple'],
|
||||
'checkbox' => ['label' => '체크박스', 'color' => 'green'],
|
||||
'date' => ['label' => '날짜', 'color' => 'orange'],
|
||||
'textarea' => ['label' => '텍스트영역', 'color' => 'gray'],
|
||||
];
|
||||
$typeInfo = $typeLabels[$field->field_type] ?? ['label' => $field->field_type, 'color' => 'gray'];
|
||||
@endphp
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-{{ $typeInfo['color'] }}-100 text-{{ $typeInfo['color'] }}-700">
|
||||
{{ $typeInfo['label'] }}
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
{{ $sourceTables[$field->source_table] ?? $field->source_table }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
@if(empty($field->field_key))
|
||||
<span class="text-gray-400 text-sm">미지정</span>
|
||||
@else
|
||||
<code class="text-sm text-gray-700 bg-gray-100 px-2 py-1 rounded">{{ $field->field_key }}</code>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 font-medium text-gray-900">
|
||||
{{ $field->field_name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
@php
|
||||
$typeLabels = [
|
||||
'textbox' => '텍스트',
|
||||
'number' => '숫자',
|
||||
'dropdown' => '드롭다운',
|
||||
'checkbox' => '체크박스',
|
||||
'date' => '날짜',
|
||||
'textarea' => '텍스트영역',
|
||||
];
|
||||
@endphp
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ $typeLabels[$field->field_type] ?? $field->field_type }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
@if($field->is_required)
|
||||
<span class="text-green-600">
|
||||
<svg class="w-5 h-5 inline" fill="currentColor" viewBox="0 0 20 20">
|
||||
</td>
|
||||
<!-- 필수 여부 -->
|
||||
<td class="px-4 py-3 text-center">
|
||||
@if($field->is_required)
|
||||
<svg class="w-4 h-4 inline text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
@else
|
||||
<span class="text-gray-300">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">
|
||||
{{ $field->default_value ?? '-' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<div class="flex justify-end gap-2">
|
||||
<button onclick="openEditModal({{ json_encode($field) }})"
|
||||
class="text-blue-600 hover:text-blue-800 text-sm font-medium">
|
||||
수정
|
||||
</button>
|
||||
<button onclick="deleteCustomField({{ $field->id }}, '{{ $field->field_name }}')"
|
||||
class="text-red-600 hover:text-red-800 text-sm font-medium">
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<span class="text-gray-300">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<!-- 설정 (options, properties, validation, display_condition) -->
|
||||
<td class="px-4 py-3 text-center">
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
@if($hasOptions)
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-700">
|
||||
옵션
|
||||
</span>
|
||||
@endif
|
||||
@if($hasProperties)
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-cyan-100 text-cyan-700">
|
||||
속성
|
||||
</span>
|
||||
@endif
|
||||
@if($hasValidation)
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-700">
|
||||
검증
|
||||
</span>
|
||||
@endif
|
||||
@if($hasDisplayCondition)
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-pink-100 text-pink-700">
|
||||
조건
|
||||
</span>
|
||||
@endif
|
||||
@if(!$hasAnyJson)
|
||||
<span class="text-gray-300 text-xs">-</span>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<!-- 액션 -->
|
||||
<td class="px-4 py-3 text-right" onclick="event.stopPropagation()">
|
||||
<div class="flex justify-end gap-1">
|
||||
<button onclick="openDetailModal({{ json_encode($field) }})"
|
||||
class="text-gray-500 hover:text-gray-700 p-1" title="상세보기">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button onclick="openEditModal({{ json_encode($field) }})"
|
||||
class="text-blue-600 hover:text-blue-800 p-1" title="수정">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</button>
|
||||
@if(!$isSystemField)
|
||||
<button onclick="deleteCustomField({{ $field->id }}, '{{ $field->field_name }}')"
|
||||
class="text-red-600 hover:text-red-800 p-1" title="삭제">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 아코디언 Row (JSON 상세) -->
|
||||
@if($hasAnyJson)
|
||||
<tr id="accordion-{{ $field->id }}" class="hidden bg-gray-50">
|
||||
<td colspan="10" class="px-4 py-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{{-- Options --}}
|
||||
@if($hasOptions)
|
||||
<div class="bg-white rounded-lg border border-purple-200 overflow-hidden">
|
||||
<div class="bg-purple-50 px-3 py-2 border-b border-purple-200">
|
||||
<span class="text-xs font-semibold text-purple-700">옵션 (Options)</span>
|
||||
</div>
|
||||
<div class="p-3">
|
||||
@if(is_array($optionsData) && !empty($optionsData))
|
||||
@if(isset($optionsData[0]) && is_array($optionsData[0]))
|
||||
{{-- 배열 형태: [{label, value}, ...] --}}
|
||||
<table class="w-full text-xs">
|
||||
<thead>
|
||||
<tr class="border-b">
|
||||
<th class="text-left py-1 text-gray-500 font-medium">Label</th>
|
||||
<th class="text-left py-1 text-gray-500 font-medium">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($optionsData as $opt)
|
||||
<tr class="border-b border-gray-100 last:border-0">
|
||||
<td class="py-1.5 text-gray-700">{{ $opt['label'] ?? '-' }}</td>
|
||||
<td class="py-1.5 font-mono text-gray-600">{{ $opt['value'] ?? '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
{{-- Key-Value 객체 형태 --}}
|
||||
@include('item-fields.partials._key-value-table', ['data' => $optionsData])
|
||||
@endif
|
||||
@else
|
||||
<span class="text-xs text-gray-400">데이터 없음</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Properties --}}
|
||||
@if($hasProperties)
|
||||
<div class="bg-white rounded-lg border border-cyan-200 overflow-hidden">
|
||||
<div class="bg-cyan-50 px-3 py-2 border-b border-cyan-200">
|
||||
<span class="text-xs font-semibold text-cyan-700">속성 (Properties)</span>
|
||||
</div>
|
||||
<div class="p-3">
|
||||
@if(is_array($propertiesData) && !empty($propertiesData))
|
||||
@include('item-fields.partials._key-value-table', ['data' => $propertiesData])
|
||||
@else
|
||||
<span class="text-xs text-gray-400">데이터 없음</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Validation Rules --}}
|
||||
@if($hasValidation)
|
||||
<div class="bg-white rounded-lg border border-yellow-200 overflow-hidden">
|
||||
<div class="bg-yellow-50 px-3 py-2 border-b border-yellow-200">
|
||||
<span class="text-xs font-semibold text-yellow-700">유효성 검사 (Validation)</span>
|
||||
</div>
|
||||
<div class="p-3">
|
||||
@if(is_array($validationData) && !empty($validationData))
|
||||
@include('item-fields.partials._key-value-table', ['data' => $validationData])
|
||||
@else
|
||||
<span class="text-xs text-gray-400">데이터 없음</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Display Condition --}}
|
||||
@if($hasDisplayCondition)
|
||||
<div class="bg-white rounded-lg border border-pink-200 overflow-hidden">
|
||||
<div class="bg-pink-50 px-3 py-2 border-b border-pink-200">
|
||||
<span class="text-xs font-semibold text-pink-700">표시 조건 (Condition)</span>
|
||||
</div>
|
||||
<div class="p-3">
|
||||
@if(is_array($displayConditionData) && !empty($displayConditionData))
|
||||
@include('item-fields.partials._key-value-table', ['data' => $displayConditionData])
|
||||
@else
|
||||
<span class="text-xs text-gray-400">데이터 없음</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 요약 정보 -->
|
||||
<div class="px-6 py-4 bg-gray-50 border-t border-gray-200">
|
||||
<div class="text-sm text-gray-600">
|
||||
총 {{ $fields->count() }}개의 커스텀 필드
|
||||
@php
|
||||
$unassignedCount = $fields->filter(fn($f) => empty($f->source_table))->count();
|
||||
@endphp
|
||||
@if($unassignedCount > 0)
|
||||
<span class="text-yellow-600">(미지정: {{ $unassignedCount }}개)</span>
|
||||
@endif
|
||||
<div class="px-4 py-3 bg-gray-50 border-t border-gray-200">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 text-sm text-gray-600">
|
||||
<div class="flex items-center gap-4">
|
||||
<span>총 {{ $fields->count() }}개</span>
|
||||
@php
|
||||
$systemCount = $fields->filter(fn($f) => $f->is_common || $f->storage_type === 'column')->count();
|
||||
$customCount = $fields->count() - $systemCount;
|
||||
$activeCount = $fields->filter(fn($f) => $f->is_active)->count();
|
||||
$lockedCount = $fields->filter(fn($f) => $f->is_locked)->count();
|
||||
$withOptionsCount = $fields->filter(fn($f) => !empty($f->options))->count();
|
||||
@endphp
|
||||
<span class="text-blue-600">시스템: {{ $systemCount }}</span>
|
||||
<span class="text-green-600">커스텀: {{ $customCount }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 text-xs">
|
||||
@if($activeCount < $fields->count())
|
||||
<span class="text-gray-500">비활성: {{ $fields->count() - $activeCount }}개</span>
|
||||
@endif
|
||||
@if($lockedCount > 0)
|
||||
<span class="text-orange-500">잠금: {{ $lockedCount }}개</span>
|
||||
@endif
|
||||
@if($withOptionsCount > 0)
|
||||
<span class="text-purple-600">옵션설정: {{ $withOptionsCount }}개</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<script>
|
||||
function toggleAccordion(fieldId) {
|
||||
const accordion = document.getElementById('accordion-' + fieldId);
|
||||
const chevron = document.getElementById('chevron-' + fieldId);
|
||||
|
||||
if (accordion.classList.contains('hidden')) {
|
||||
accordion.classList.remove('hidden');
|
||||
chevron.classList.add('rotate-90');
|
||||
} else {
|
||||
accordion.classList.add('hidden');
|
||||
chevron.classList.remove('rotate-90');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endif
|
||||
@@ -24,10 +24,19 @@
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@foreach($statuses as $sourceTable => $status)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<tr class="hover:bg-gray-50 cursor-pointer transition-colors"
|
||||
onclick="goToFieldManagement('{{ $sourceTable }}')"
|
||||
title="클릭하여 {{ $status['label'] }} 필드 목록 보기">
|
||||
<td class="px-6 py-4">
|
||||
<div class="font-medium text-gray-900">{{ $status['label'] }}</div>
|
||||
<div class="text-xs text-gray-500">{{ $sourceTable }}</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div>
|
||||
<div class="font-medium text-gray-900">{{ $status['label'] }}</div>
|
||||
<code class="text-xs text-gray-500 font-mono">{{ $sourceTable }}</code>
|
||||
</div>
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center text-sm text-gray-500">
|
||||
{{ $status['total_count'] }}개
|
||||
@@ -50,7 +59,7 @@
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<td class="px-6 py-4 text-right" onclick="event.stopPropagation()">
|
||||
<div class="flex justify-end gap-2">
|
||||
@if($status['status'] !== 'complete')
|
||||
<button onclick="seedTable('{{ $sourceTable }}')"
|
||||
@@ -79,8 +88,8 @@ class="text-red-600 hover:text-red-800 text-sm font-medium">
|
||||
{{ collect($statuses)->where('status', 'complete')->count() }}개 완료
|
||||
</span>
|
||||
<span class="text-xs text-gray-400">
|
||||
* 시딩: 시스템 필드 등록 / 초기화: 삭제 후 재시딩
|
||||
* 행 클릭: 필드 목록 보기 / 시딩: 시스템 필드 등록 / 초기화: 삭제 후 재시딩
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
Reference in New Issue
Block a user