Files
sam-manage/resources/views/layouts/tenant-console.blade.php
강영보 d03c7ed870 feat: [bending] 절곡품 관리 MNG 화면
- 기초관리: 목록(13컬럼) + 폼(기본정보 + 케이스전용 + 절곡테이블 + 이미지)
- 절곡품: 가이드레일/케이스/하단마감재 타입별 목록 + 폼
- 부품 추가(기초관리 검색 모달) + 삭제 + 수량/품명/재질 편집
- 절곡테이블 inline 편집 + 재질별 폭합 자동계산
- 작업지시서 레거시 포맷 인쇄 모달
- 원본수정 버튼 sam_item_id 직접 링크
- DB 메뉴 등록 (기초관리 + 절곡품 + 케이스 + 하단마감재)
2026-03-19 21:08:57 +09:00

177 lines
8.0 KiB
PHP

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title', '테넌트 콘솔') - {{ $consoleTenant->company_name ?? '' }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/remixicon@4.6.0/fonts/remixicon.min.css">
<script>
window.SAM_CONFIG = {
apiBaseUrl: '{{ config('services.api.base_url', 'https://api.codebridge-x.com') }}',
apiKey: '{{ config('services.api.key', '') }}',
consoleTenantId: {{ $consoleTenantId ?? 0 }},
};
</script>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
document.addEventListener('htmx:configRequest', (event) => {
const csrfToken = document.querySelector('meta[name="csrf-token"]');
if (csrfToken) {
event.detail.headers['X-CSRF-TOKEN'] = csrfToken.getAttribute('content');
}
// 테넌트 콘솔 컨텍스트 자동 주입 (API 호출 시 테넌트 해제 방지)
if (window.SAM_CONFIG && window.SAM_CONFIG.consoleTenantId) {
event.detail.parameters['tenant_console_id'] = window.SAM_CONFIG.consoleTenantId;
event.detail.parameters['tenant_id'] = window.SAM_CONFIG.consoleTenantId;
}
});
// fetch() 호출에도 테넌트 콘솔 컨텍스트 자동 주입
(function() {
if (!window.SAM_CONFIG || !window.SAM_CONFIG.consoleTenantId) return;
const tenantId = window.SAM_CONFIG.consoleTenantId;
const originalFetch = window.fetch;
window.fetch = function(url, options) {
options = options || {};
const method = (options.method || 'GET').toUpperCase();
// GET 요청: URL 파라미터에 추가
if (method === 'GET' && typeof url === 'string' && url.includes('/api/')) {
const separator = url.includes('?') ? '&' : '?';
url = url + separator + 'tenant_console_id=' + tenantId + '&tenant_id=' + tenantId;
}
// POST/PUT/DELETE 요청: body에 추가
if (method !== 'GET' && typeof url === 'string' && url.includes('/api/')) {
const contentType = (options.headers && (options.headers['Content-Type'] || options.headers['content-type'])) || '';
if (contentType.includes('application/json') && options.body) {
try {
const body = JSON.parse(options.body);
body.tenant_console_id = tenantId;
body.tenant_id = tenantId;
options.body = JSON.stringify(body);
} catch(e) {}
}
}
return originalFetch.call(this, url, options);
};
})();
</script>
<style>
select { padding-right: 2rem !important; }
</style>
@stack('styles')
</head>
<body class="bg-gray-100">
<div class="flex h-screen overflow-hidden">
<!-- Sidebar -->
@include('partials.tenant-console-sidebar')
<!-- Main Content -->
<div class="flex-1 flex flex-col overflow-hidden min-w-0">
<!-- Header -->
<header class="bg-white border-b border-gray-200 px-6 py-3 flex items-center justify-between shrink-0">
<div class="flex items-center gap-3">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
<i class="ri-building-line mr-1.5"></i>
{{ $consoleTenant->company_name ?? '테넌트' }}
</span>
<span class="text-sm text-gray-500">테넌트 관리 콘솔</span>
</div>
<button onclick="window.close()" class="text-gray-400 hover:text-gray-600 transition" title="창 닫기">
<i class="ri-close-line text-xl"></i>
</button>
</header>
<!-- Page Content -->
<main class="flex-1 overflow-y-auto bg-gray-100 p-6">
@yield('content')
</main>
</div>
</div>
<!-- SweetAlert2 공통 함수 -->
<script>
const SwalTailwind = Swal.mixin({
customClass: {
popup: 'rounded-xl shadow-2xl border-0',
title: 'text-gray-900 font-semibold',
htmlContainer: 'text-gray-600',
confirmButton: 'bg-blue-600 hover:bg-blue-700 text-white font-medium px-5 py-2.5 rounded-lg transition-colors',
cancelButton: 'bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium px-5 py-2.5 rounded-lg transition-colors',
actions: 'gap-3',
},
buttonsStyling: false,
});
const Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
customClass: { popup: 'rounded-lg shadow-lg' },
didOpen: (toast) => {
toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer;
}
});
function showToast(message, type = 'info', timer = 3000) {
Toast.fire({ icon: type, title: message, timer });
}
function showConfirm(message, onConfirm, options = {}) {
SwalTailwind.fire({
title: options.title || '확인',
icon: options.icon || 'question',
html: message,
confirmButtonText: options.confirmText || '확인',
cancelButtonText: options.cancelText || '취소',
showCancelButton: true,
reverseButtons: true,
}).then((result) => {
if (result.isConfirmed && typeof onConfirm === 'function') onConfirm();
});
}
function showDeleteConfirm(itemName, onConfirm) {
SwalTailwind.fire({
title: '삭제 확인',
html: `<span class="text-red-600 font-medium">"${itemName}"</span>을(를) 삭제하시겠습니까?`,
icon: 'warning',
showCancelButton: true,
confirmButtonText: '삭제',
cancelButtonText: '취소',
reverseButtons: true,
customClass: {
popup: 'rounded-xl shadow-2xl border-0',
confirmButton: 'bg-red-600 hover:bg-red-700 text-white font-medium px-5 py-2.5 rounded-lg',
cancelButton: 'bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium px-5 py-2.5 rounded-lg',
actions: 'gap-3',
},
buttonsStyling: false,
}).then((result) => {
if (result.isConfirmed && typeof onConfirm === 'function') onConfirm();
});
}
function showSuccess(message, onClose = null) {
SwalTailwind.fire({ title: '완료', text: message, icon: 'success', confirmButtonText: '확인' })
.then(() => { if (typeof onClose === 'function') onClose(); });
}
function showError(message) {
SwalTailwind.fire({ title: '오류', text: message, icon: 'error', confirmButtonText: '확인' });
}
</script>
<script src="{{ asset('js/pagination.js') }}"></script>
<script src="{{ asset('js/table-sort.js') }}"></script>
@stack('scripts')
</body>
</html>