fix: [bending] API 연결 실패 시 안내 메시지 표시
- API 401/403/연결실패 시 구체적인 안내 메시지 표시 - 데이터 없음과 API 오류를 구분하여 사용자에게 안내
This commit is contained in:
@@ -37,8 +37,27 @@ public function index(Request $request): View|\Illuminate\Http\Response
|
||||
$params = $request->only(['item_sep', 'item_bending', 'material', 'model_UA', 'item_name', 'search', 'page', 'size']);
|
||||
$params['size'] = $params['size'] ?? 30;
|
||||
|
||||
$response = $this->api()->get('/api/v1/bending-items', $params);
|
||||
$body = $response->successful() ? $response->json('data', []) : [];
|
||||
$apiError = null;
|
||||
try {
|
||||
$response = $this->api()->get('/api/v1/bending-items', $params);
|
||||
} catch (\Illuminate\Http\Client\ConnectionException $e) {
|
||||
$apiError = 'API 서버에 연결할 수 없습니다. API 서비스 상태를 확인해 주세요.';
|
||||
$response = null;
|
||||
}
|
||||
|
||||
if ($response && $response->successful()) {
|
||||
$body = $response->json('data', []);
|
||||
} else {
|
||||
$body = [];
|
||||
if (! $apiError && $response) {
|
||||
$apiError = match ($response->status()) {
|
||||
401 => 'API 인증이 필요합니다. SAM 서비스에 로그인하여 API를 연결해 주세요.',
|
||||
403 => 'API 접근 권한이 없습니다. 관리자에게 문의해 주세요.',
|
||||
default => "API 오류가 발생했습니다. (HTTP {$response->status()})",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'data' => $body['data'] ?? [],
|
||||
'total' => $body['total'] ?? 0,
|
||||
@@ -46,12 +65,15 @@ public function index(Request $request): View|\Illuminate\Http\Response
|
||||
'last_page' => $body['last_page'] ?? 1,
|
||||
];
|
||||
|
||||
$filterResponse = $this->api()->get('/api/v1/bending-items/filters');
|
||||
$filterOptions = $filterResponse->successful() ? $filterResponse->json('data', []) : [];
|
||||
$filterOptions = [];
|
||||
if (! $apiError) {
|
||||
$filterResponse = $this->api()->get('/api/v1/bending-items/filters');
|
||||
$filterOptions = $filterResponse->successful() ? $filterResponse->json('data', []) : [];
|
||||
}
|
||||
|
||||
if ($request->header('HX-Request')) {
|
||||
if ($request->header('HX-Target') === 'items-table') {
|
||||
return view('bending.base.partials.table', ['items' => $data]);
|
||||
return view('bending.base.partials.table', ['items' => $data, 'apiError' => $apiError]);
|
||||
}
|
||||
|
||||
return response('', 200)->header('HX-Redirect', route('bending.base.index', $request->query()));
|
||||
@@ -60,6 +82,7 @@ public function index(Request $request): View|\Illuminate\Http\Response
|
||||
return view('bending.base.index', [
|
||||
'items' => $data,
|
||||
'filterOptions' => $filterOptions,
|
||||
'apiError' => $apiError,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,8 +49,27 @@ public function index(Request $request, string $category = 'GUIDERAIL_MODEL'): V
|
||||
$params['size'] = $params['size'] ?? 30;
|
||||
$params['item_category'] = $category;
|
||||
|
||||
$response = $this->api()->get('/api/v1/guiderail-models', $params);
|
||||
$body = $response->successful() ? $response->json('data', []) : [];
|
||||
$apiError = null;
|
||||
try {
|
||||
$response = $this->api()->get('/api/v1/guiderail-models', $params);
|
||||
} catch (\Illuminate\Http\Client\ConnectionException $e) {
|
||||
$apiError = 'API 서버에 연결할 수 없습니다. API 서비스 상태를 확인해 주세요.';
|
||||
$response = null;
|
||||
}
|
||||
|
||||
if ($response && $response->successful()) {
|
||||
$body = $response->json('data', []);
|
||||
} else {
|
||||
$body = [];
|
||||
if (! $apiError && $response) {
|
||||
$apiError = match ($response->status()) {
|
||||
401 => 'API 인증이 필요합니다. SAM 서비스에 로그인하여 API를 연결해 주세요.',
|
||||
403 => 'API 접근 권한이 없습니다. 관리자에게 문의해 주세요.',
|
||||
default => "API 오류가 발생했습니다. (HTTP {$response->status()})",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'data' => $body['data'] ?? [],
|
||||
'total' => $body['total'] ?? 0,
|
||||
@@ -58,16 +77,17 @@ public function index(Request $request, string $category = 'GUIDERAIL_MODEL'): V
|
||||
'last_page' => $body['last_page'] ?? 1,
|
||||
];
|
||||
|
||||
$filterResponse = $this->api()->get('/api/v1/guiderail-models/filters');
|
||||
$filterOptions = $filterResponse->successful() ? $filterResponse->json('data', []) : [];
|
||||
$filterOptions = [];
|
||||
if (! $apiError) {
|
||||
$filterResponse = $this->api()->get('/api/v1/guiderail-models/filters');
|
||||
$filterOptions = $filterResponse->successful() ? $filterResponse->json('data', []) : [];
|
||||
}
|
||||
|
||||
if ($request->header('HX-Request')) {
|
||||
// 필터/검색 HTMX (hx-target="#items-table") → 파셜 반환
|
||||
if ($request->header('HX-Target') === 'items-table') {
|
||||
return view('bending.products.partials.table', ['items' => $data, 'config' => $config, 'category' => $category]);
|
||||
return view('bending.products.partials.table', ['items' => $data, 'config' => $config, 'category' => $category, 'apiError' => $apiError]);
|
||||
}
|
||||
|
||||
// 사이드바 등 그 외 HTMX → 전체 페이지 리로드
|
||||
return response('', 200)->header('HX-Redirect', route("bending.{$config['prefix']}.index", $request->query()));
|
||||
}
|
||||
|
||||
@@ -76,6 +96,7 @@ public function index(Request $request, string $category = 'GUIDERAIL_MODEL'): V
|
||||
'filterOptions' => $filterOptions,
|
||||
'config' => $config,
|
||||
'category' => $category,
|
||||
'apiError' => $apiError,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
179
resources/views/bending/base/partials/table.blade.php
Normal file
179
resources/views/bending/base/partials/table.blade.php
Normal file
@@ -0,0 +1,179 @@
|
||||
@php
|
||||
$itemList = $items['data'] ?? $items ?? [];
|
||||
$total = $items['total'] ?? count($itemList);
|
||||
$currentPage = $items['current_page'] ?? 1;
|
||||
$lastPage = $items['last_page'] ?? 1;
|
||||
@endphp
|
||||
|
||||
<div class="overflow-x-auto bg-white rounded-lg shadow-sm mt-4">
|
||||
<table class="min-w-full text-sm">
|
||||
<thead class="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">NO</th>
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">코드</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">대분류</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">인정</th>
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">분류</th>
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">품명</th>
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">규격</th>
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">재질</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">이미지</th>
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">모델</th>
|
||||
<th class="px-2 py-2 text-right text-sm font-semibold text-gray-700">폭합</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">절곡수</th>
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">등록일</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">수정자</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@forelse($itemList as $item)
|
||||
@php
|
||||
$itemSep = $item['item_sep'] ?? '-';
|
||||
$widthSum = $item['width_sum'] ?? null;
|
||||
$bendCount = $item['bend_count'] ?? 0;
|
||||
$modelUA = $item['model_UA'] ?? null;
|
||||
$createdAt = $item['created_at'] ?? null;
|
||||
@endphp
|
||||
<tr class="hover:bg-blue-50 cursor-pointer" onclick="window.location='{{ route('bending.base.show', $item['id']) }}'">
|
||||
<td class="px-2 py-2 text-gray-500 text-xs">{{ $item['id'] }}</td>
|
||||
<td class="px-2 py-2 font-mono text-xs">{{ $item['code'] }}</td>
|
||||
<td class="px-2 py-2 text-center">
|
||||
<span class="px-1.5 py-0.5 rounded text-xs {{ $itemSep === '스크린' ? 'bg-blue-100 text-blue-700' : 'bg-orange-100 text-orange-700' }}">
|
||||
{{ $itemSep }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center text-xs">
|
||||
@if($modelUA)
|
||||
<span class="{{ $modelUA === '인정' ? 'text-green-600' : 'text-gray-400' }}">{{ $modelUA }}</span>
|
||||
@else
|
||||
<span class="text-gray-300">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-2 py-2 text-xs">{{ $item['item_bending'] ?? '-' }}</td>
|
||||
<td class="px-2 py-2 font-medium text-xs">{{ $item['item_name'] ?? $item['name'] }}</td>
|
||||
<td class="px-2 py-2 text-gray-600 text-xs">{{ $item['item_spec'] ?? '-' }}</td>
|
||||
<td class="px-2 py-2 text-gray-600 text-xs">{{ $item['material'] ?? '-' }}</td>
|
||||
<td class="px-2 py-2 text-center">
|
||||
@if(!empty($item['image_file_id']))
|
||||
<div class="relative inline-block img-preview-wrap">
|
||||
<img src="{{ $item['image_url'] ?? route('files.view', $item['image_file_id']) }}" width="24" height="24" style="width:24px; height:24px; object-fit:contain;" class="rounded inline-block" alt="">
|
||||
<div class="img-preview-popup hidden absolute z-50 bg-white border shadow-xl rounded-lg p-1" style="left:50%; transform:translateX(-50%); width:300px;">
|
||||
<img src="{{ $item['image_url'] ?? route('files.view', $item['image_file_id']) }}" class="w-full rounded" alt="">
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<span class="text-gray-300">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-2 py-2 text-gray-600 text-xs">{{ $item['model_name'] ?? '-' }}</td>
|
||||
<td class="px-2 py-2 text-right font-mono text-xs">{{ $widthSum ?? '-' }}</td>
|
||||
<td class="px-2 py-2 text-center text-xs">
|
||||
@if($bendCount > 0)
|
||||
<span class="text-blue-600 font-medium">{{ $bendCount }}</span>
|
||||
@else
|
||||
<span class="text-gray-300">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-2 py-2 text-gray-500 text-xs">{{ $createdAt ? \Illuminate\Support\Str::before($createdAt, ' ') : '-' }}</td>
|
||||
<td class="px-2 py-2 text-center text-xs text-gray-500">{{ $item['modified_by'] ?? '-' }}</td>
|
||||
<td class="px-2 py-2 text-center">
|
||||
<a href="{{ route('bending.base.edit', $item['id']) }}" class="text-blue-600 hover:underline text-xs"
|
||||
onclick="event.stopPropagation()">수정</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="15" class="px-3 py-8 text-center">
|
||||
@if(!empty($apiError))
|
||||
<div class="text-amber-600">
|
||||
<svg class="w-8 h-8 mx-auto mb-2 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/>
|
||||
</svg>
|
||||
<p class="text-sm font-medium">{{ $apiError }}</p>
|
||||
</div>
|
||||
@else
|
||||
<span class="text-gray-400">데이터가 없습니다.</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('.img-preview-wrap').forEach(wrap => {
|
||||
const popup = wrap.querySelector('.img-preview-popup');
|
||||
if (!popup) return;
|
||||
popup.style.position = 'fixed';
|
||||
wrap.addEventListener('mouseenter', () => {
|
||||
const rect = wrap.getBoundingClientRect();
|
||||
const popW = 300;
|
||||
let left = rect.left + rect.width / 2 - popW / 2;
|
||||
if (left < 4) left = 4;
|
||||
if (left + popW > window.innerWidth - 4) left = window.innerWidth - popW - 4;
|
||||
popup.style.left = left + 'px';
|
||||
popup.style.width = popW + 'px';
|
||||
popup.style.transform = 'none';
|
||||
if (rect.top > window.innerHeight / 2) {
|
||||
popup.style.bottom = (window.innerHeight - rect.top + 4) + 'px';
|
||||
popup.style.top = 'auto';
|
||||
} else {
|
||||
popup.style.top = (rect.bottom + 4) + 'px';
|
||||
popup.style.bottom = 'auto';
|
||||
}
|
||||
popup.classList.remove('hidden');
|
||||
});
|
||||
wrap.addEventListener('mouseleave', () => { popup.classList.add('hidden'); });
|
||||
});
|
||||
</script>
|
||||
|
||||
{{-- 페이지네이션 --}}
|
||||
@if($lastPage > 1)
|
||||
<div class="bg-white px-4 py-3 mt-4 rounded-lg shadow-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm text-gray-700">전체 <span class="font-medium">{{ $total }}</span>건</p>
|
||||
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px">
|
||||
@if($currentPage > 1)
|
||||
<a href="{{ request()->fullUrlWithQuery(['page' => 1]) }}" class="relative inline-flex items-center px-3 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">처음</a>
|
||||
<a href="{{ request()->fullUrlWithQuery(['page' => $currentPage - 1]) }}" class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
|
||||
</a>
|
||||
@else
|
||||
<span class="relative inline-flex items-center px-3 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">처음</span>
|
||||
<span class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
|
||||
</span>
|
||||
@endif
|
||||
|
||||
@php
|
||||
$maxPages = 10;
|
||||
$startPage = max(1, $currentPage - floor($maxPages / 2));
|
||||
$endPage = min($lastPage, $startPage + $maxPages - 1);
|
||||
if ($endPage - $startPage + 1 < $maxPages) { $startPage = max(1, $endPage - $maxPages + 1); }
|
||||
@endphp
|
||||
|
||||
@for($p = $startPage; $p <= $endPage; $p++)
|
||||
@if($p == $currentPage)
|
||||
<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-blue-50 text-sm font-medium text-blue-600">{{ $p }}</span>
|
||||
@else
|
||||
<a href="{{ request()->fullUrlWithQuery(['page' => $p]) }}" class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">{{ $p }}</a>
|
||||
@endif
|
||||
@endfor
|
||||
|
||||
@if($currentPage < $lastPage)
|
||||
<a href="{{ request()->fullUrlWithQuery(['page' => $currentPage + 1]) }}" class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>
|
||||
</a>
|
||||
<a href="{{ request()->fullUrlWithQuery(['page' => $lastPage]) }}" class="relative inline-flex items-center px-3 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">끝</a>
|
||||
@else
|
||||
<span class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>
|
||||
</span>
|
||||
<span class="relative inline-flex items-center px-3 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">끝</span>
|
||||
@endif
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
235
resources/views/bending/products/partials/table.blade.php
Normal file
235
resources/views/bending/products/partials/table.blade.php
Normal file
@@ -0,0 +1,235 @@
|
||||
@php
|
||||
$itemList = $items['data'] ?? [];
|
||||
$total = $items['total'] ?? count($itemList);
|
||||
$currentPage = $items['current_page'] ?? 1;
|
||||
$lastPage = $items['last_page'] ?? 1;
|
||||
$cat = $category ?? ($config['category'] ?? 'GUIDERAIL_MODEL');
|
||||
$isCase = $cat === 'SHUTTERBOX_MODEL';
|
||||
$isBottom = $cat === 'BOTTOMBAR_MODEL';
|
||||
$isGuiderail = !$isCase && !$isBottom;
|
||||
@endphp
|
||||
|
||||
<div class="overflow-x-auto bg-white rounded-lg shadow-sm mt-4">
|
||||
<table class="min-w-full text-sm">
|
||||
<thead class="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">NO</th>
|
||||
@if($isCase)
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">박스(가로×세로)</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">점검구</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">전면밑</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">레일폭</th>
|
||||
@elseif($isBottom)
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">모델명</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">대분류</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">인정</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">가로×세로</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">마감</th>
|
||||
@else
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">모델명</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">대분류</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">인정</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">형상</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">레일폭×높이</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">마감</th>
|
||||
@endif
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">이미지</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">부품수</th>
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">소요자재량</th>
|
||||
<th class="px-2 py-2 text-left text-sm font-semibold text-gray-700">검색어</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">수정자</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">작업지시서</th>
|
||||
<th class="px-2 py-2 text-center text-sm font-semibold text-gray-700">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@forelse($itemList as $item)
|
||||
@php
|
||||
$itemCat = $item['item_category'] ?? 'GUIDERAIL_MODEL';
|
||||
$routePrefix = match($itemCat) { 'SHUTTERBOX_MODEL' => 'cases', 'BOTTOMBAR_MODEL' => 'bottombars', default => 'products' };
|
||||
@endphp
|
||||
<tr class="hover:bg-blue-50 cursor-pointer" onclick="window.location='{{ route("bending.{$routePrefix}.show", $item['id']) }}'">
|
||||
<td class="px-2 py-2 text-gray-500 text-xs">{{ $item['id'] }}</td>
|
||||
@if($isCase)
|
||||
<td class="px-2 py-2 text-center text-xs font-mono font-bold">
|
||||
<span class="text-blue-600">{{ $item['box_width'] ?? '-' }}</span>×<span class="text-red-600">{{ $item['box_height'] ?? '-' }}</span>
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center text-xs">{{ $item['exit_direction'] ?? '-' }}</td>
|
||||
<td class="px-2 py-2 text-center text-xs">{{ $item['front_bottom_width'] ?? '-' }}</td>
|
||||
<td class="px-2 py-2 text-center text-xs">{{ $item['rail_width'] ?? '-' }}</td>
|
||||
@elseif($isBottom)
|
||||
<td class="px-2 py-2 font-medium"><span class="text-blue-700">{{ $item['model_name'] ?? $item['name'] }}</span></td>
|
||||
<td class="px-2 py-2 text-center">
|
||||
@php $sep = $item['item_sep'] ?? '-'; @endphp
|
||||
<span class="px-1.5 py-0.5 rounded text-xs {{ $sep === '스크린' ? 'bg-blue-100 text-blue-700' : 'bg-orange-100 text-orange-700' }}">{{ $sep }}</span>
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center text-xs">
|
||||
@if($item['model_UA'] ?? null)
|
||||
<span class="{{ $item['model_UA'] === '인정' ? 'text-green-600' : 'text-gray-400' }}">{{ $item['model_UA'] }}</span>
|
||||
@else - @endif
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center text-xs font-mono">{{ ($item['bar_width'] ?? '-') }}×{{ ($item['bar_height'] ?? '-') }}</td>
|
||||
<td class="px-2 py-2 text-center text-xs">{{ $item['finishing_type'] ?? '-' }}</td>
|
||||
@else
|
||||
<td class="px-2 py-2 font-medium"><span class="text-blue-700">{{ $item['model_name'] ?? $item['name'] }}</span></td>
|
||||
<td class="px-2 py-2 text-center">
|
||||
@php $sep = $item['item_sep'] ?? '-'; @endphp
|
||||
<span class="px-1.5 py-0.5 rounded text-xs {{ $sep === '스크린' ? 'bg-blue-100 text-blue-700' : 'bg-orange-100 text-orange-700' }}">{{ $sep }}</span>
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center text-xs">
|
||||
@if($item['model_UA'] ?? null)
|
||||
<span class="{{ $item['model_UA'] === '인정' ? 'text-green-600' : 'text-gray-400' }}">{{ $item['model_UA'] }}</span>
|
||||
@else - @endif
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center text-xs">{{ $item['check_type'] ?? '-' }}</td>
|
||||
<td class="px-2 py-2 text-center text-xs font-mono">{{ ($item['rail_width'] ?? '-') }}×{{ ($item['rail_length'] ?? '-') }}</td>
|
||||
<td class="px-2 py-2 text-center text-xs">{{ $item['finishing_type'] ?? '-' }}</td>
|
||||
@endif
|
||||
<td class="px-2 py-2 text-center">
|
||||
@if(!empty($item['image_file_id']))
|
||||
<div class="relative inline-block img-preview-wrap">
|
||||
<img src="{{ $item['image_url'] ?? route('files.view', $item['image_file_id']) }}" width="24" height="24" style="width:24px; height:24px; object-fit:contain;" class="rounded inline-block" alt="">
|
||||
<div class="img-preview-popup hidden absolute z-50 bg-white border shadow-xl rounded-lg p-1" style="left:50%; transform:translateX(-50%); width:300px;">
|
||||
<img src="{{ $item['image_url'] ?? route('files.view', $item['image_file_id']) }}" class="w-full rounded" alt="">
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<span class="text-gray-300">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center text-xs">
|
||||
<span class="px-1.5 py-0.5 bg-gray-100 rounded">{{ $item['component_count'] ?? 0 }}</span>
|
||||
</td>
|
||||
<td class="px-2 py-2 text-xs">
|
||||
@foreach(($item['material_summary'] ?? []) as $mat => $total)
|
||||
<span class="text-gray-600">{{ $mat }}: <strong>{{ $total }}</strong></span>
|
||||
@if(!$loop->last) | @endif
|
||||
@endforeach
|
||||
@if(empty($item['material_summary'])) - @endif
|
||||
</td>
|
||||
<td class="px-2 py-2 text-xs text-gray-500">{{ $item['search_keyword'] ?? '-' }}</td>
|
||||
<td class="px-2 py-2 text-xs text-gray-500">{{ $item['modified_by'] ?? '-' }}</td>
|
||||
<td class="px-2 py-2 text-center">
|
||||
@if(!empty($item['id']))
|
||||
<button type="button" onclick="event.stopPropagation(); openPrintModal('{{ route("bending.{$routePrefix}.print", $item['id']) }}')"
|
||||
class="px-2 py-0.5 bg-gray-600 text-white rounded text-xs hover:bg-gray-700">보기</button>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-2 py-2 text-center">
|
||||
<a href="{{ route("bending.{$routePrefix}.edit", $item['id']) }}" class="text-blue-600 hover:underline text-xs"
|
||||
onclick="event.stopPropagation()">수정</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="20" class="px-3 py-8 text-center">
|
||||
@if(!empty($apiError))
|
||||
<div class="text-amber-600">
|
||||
<svg class="w-8 h-8 mx-auto mb-2 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/>
|
||||
</svg>
|
||||
<p class="text-sm font-medium">{{ $apiError }}</p>
|
||||
</div>
|
||||
@else
|
||||
<span class="text-gray-400">데이터가 없습니다.</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('.img-preview-wrap').forEach(wrap => {
|
||||
const popup = wrap.querySelector('.img-preview-popup');
|
||||
if (!popup) return;
|
||||
popup.style.position = 'fixed';
|
||||
wrap.addEventListener('mouseenter', () => {
|
||||
const rect = wrap.getBoundingClientRect();
|
||||
const popW = 300;
|
||||
let left = rect.left + rect.width / 2 - popW / 2;
|
||||
if (left < 4) left = 4;
|
||||
if (left + popW > window.innerWidth - 4) left = window.innerWidth - popW - 4;
|
||||
popup.style.left = left + 'px';
|
||||
popup.style.width = popW + 'px';
|
||||
popup.style.transform = 'none';
|
||||
if (rect.top > window.innerHeight / 2) {
|
||||
popup.style.bottom = (window.innerHeight - rect.top + 4) + 'px';
|
||||
popup.style.top = 'auto';
|
||||
} else {
|
||||
popup.style.top = (rect.bottom + 4) + 'px';
|
||||
popup.style.bottom = 'auto';
|
||||
}
|
||||
popup.classList.remove('hidden');
|
||||
});
|
||||
wrap.addEventListener('mouseleave', () => { popup.classList.add('hidden'); });
|
||||
});
|
||||
</script>
|
||||
|
||||
{{-- 작업지시서 모달 (iframe 방식) --}}
|
||||
<dialog id="printModal" class="rounded-lg shadow-2xl p-0 backdrop:bg-black/50" style="max-width:95vw; max-height:95vh; width:1400px; height:90vh; border:none;">
|
||||
<div class="flex items-center justify-between px-4 py-2 bg-gray-100 border-b shrink-0">
|
||||
<span class="text-sm font-bold text-gray-700">작업지시서</span>
|
||||
<div class="flex gap-2">
|
||||
<button type="button" onclick="document.getElementById('printFrame').contentWindow.print()" class="px-3 py-1 bg-blue-600 text-white rounded text-xs hover:bg-blue-700">PDF 다운로드</button>
|
||||
<button type="button" onclick="document.getElementById('printModal').close()" class="px-3 py-1 bg-gray-400 text-white rounded text-xs hover:bg-gray-500">닫기</button>
|
||||
</div>
|
||||
</div>
|
||||
<iframe id="printFrame" class="w-full" style="height:calc(90vh - 44px); border:none;"></iframe>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
function openPrintModal(url) {
|
||||
document.getElementById('printFrame').src = url;
|
||||
document.getElementById('printModal').showModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
@if($lastPage > 1)
|
||||
<div class="bg-white px-4 py-3 mt-4 rounded-lg shadow-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm text-gray-700">전체 <span class="font-medium">{{ $total }}</span>건</p>
|
||||
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px">
|
||||
@if($currentPage > 1)
|
||||
<a href="{{ request()->fullUrlWithQuery(['page' => 1]) }}" class="relative inline-flex items-center px-3 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">처음</a>
|
||||
<a href="{{ request()->fullUrlWithQuery(['page' => $currentPage - 1]) }}" class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
|
||||
</a>
|
||||
@else
|
||||
<span class="relative inline-flex items-center px-3 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">처음</span>
|
||||
<span class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
|
||||
</span>
|
||||
@endif
|
||||
|
||||
@php
|
||||
$maxPages = 10;
|
||||
$startPage = max(1, $currentPage - floor($maxPages / 2));
|
||||
$endPage = min($lastPage, $startPage + $maxPages - 1);
|
||||
if ($endPage - $startPage + 1 < $maxPages) { $startPage = max(1, $endPage - $maxPages + 1); }
|
||||
@endphp
|
||||
|
||||
@for($p = $startPage; $p <= $endPage; $p++)
|
||||
@if($p == $currentPage)
|
||||
<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-blue-50 text-sm font-medium text-blue-600">{{ $p }}</span>
|
||||
@else
|
||||
<a href="{{ request()->fullUrlWithQuery(['page' => $p]) }}" class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">{{ $p }}</a>
|
||||
@endif
|
||||
@endfor
|
||||
|
||||
@if($currentPage < $lastPage)
|
||||
<a href="{{ request()->fullUrlWithQuery(['page' => $currentPage + 1]) }}" class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>
|
||||
</a>
|
||||
<a href="{{ request()->fullUrlWithQuery(['page' => $lastPage]) }}" class="relative inline-flex items-center px-3 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">끝</a>
|
||||
@else
|
||||
<span class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>
|
||||
</span>
|
||||
<span class="relative inline-flex items-center px-3 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">끝</span>
|
||||
@endif
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
Reference in New Issue
Block a user