- 기초관리: 목록(13컬럼) + 폼(기본정보 + 케이스전용 + 절곡테이블 + 이미지) - 절곡품: 가이드레일/케이스/하단마감재 타입별 목록 + 폼 - 부품 추가(기초관리 검색 모달) + 삭제 + 수량/품명/재질 편집 - 절곡테이블 inline 편집 + 재질별 폭합 자동계산 - 작업지시서 레거시 포맷 인쇄 모달 - 원본수정 버튼 sam_item_id 직접 링크 - DB 메뉴 등록 (기초관리 + 절곡품 + 케이스 + 하단마감재)
177 lines
8.0 KiB
PHP
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>
|