- ItemFieldController API 수정 - ItemFieldSeedingService 로직 개선 - Flow Tester 상세 화면 개선 - 레이아웃 및 프로젝트 상세 화면 수정 - 테이블 정렬 JS 추가
220 lines
8.4 KiB
PHP
220 lines
8.4 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', 'Dashboard') - {{ config('app.name') }}</title>
|
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
|
<!-- SweetAlert2 -->
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
@stack('styles')
|
|
</head>
|
|
<body class="bg-gray-100">
|
|
<div class="flex h-screen overflow-hidden">
|
|
<!-- Sidebar -->
|
|
@include('partials.sidebar')
|
|
|
|
<!-- Main Content Area -->
|
|
<div class="flex-1 flex flex-col overflow-hidden">
|
|
<!-- Header -->
|
|
@include('partials.header')
|
|
|
|
<!-- Page Content -->
|
|
<main class="flex-1 overflow-y-auto bg-gray-100 p-6">
|
|
@yield('content')
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 전역 컨텍스트 메뉴 -->
|
|
@include('components.context-menu')
|
|
|
|
<!-- 테넌트 정보 모달 -->
|
|
@include('components.tenant-modal')
|
|
|
|
<!-- 사용자 정보 모달 -->
|
|
@include('components.user-modal')
|
|
|
|
<!-- HTMX CSRF 토큰 설정 -->
|
|
<script>
|
|
document.body.addEventListener('htmx:configRequest', (event) => {
|
|
event.detail.headers['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
|
});
|
|
</script>
|
|
|
|
<script src="{{ asset('js/pagination.js') }}"></script>
|
|
<script src="{{ asset('js/table-sort.js') }}"></script>
|
|
<script src="{{ asset('js/context-menu.js') }}"></script>
|
|
<script src="{{ asset('js/tenant-modal.js') }}"></script>
|
|
<script src="{{ asset('js/user-modal.js') }}"></script>
|
|
|
|
<!-- SweetAlert2 공통 함수 (Tailwind 테마) -->
|
|
<script>
|
|
// Tailwind 커스텀 클래스
|
|
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 focus:ring-4 focus:ring-blue-300',
|
|
cancelButton: 'bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium px-5 py-2.5 rounded-lg transition-colors focus:ring-4 focus:ring-gray-100',
|
|
denyButton: 'bg-red-600 hover:bg-red-700 text-white font-medium px-5 py-2.5 rounded-lg transition-colors focus:ring-4 focus:ring-red-300',
|
|
actions: 'gap-3',
|
|
},
|
|
buttonsStyling: false,
|
|
});
|
|
|
|
// Toast (스낵바) - 우상단 알림
|
|
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;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 토스트 알림 표시
|
|
* @param {string} message - 메시지
|
|
* @param {string} type - 'success' | 'error' | 'warning' | 'info'
|
|
* @param {number} timer - 표시 시간 (ms), 기본 3000
|
|
*/
|
|
function showToast(message, type = 'info', timer = 3000) {
|
|
Toast.fire({
|
|
icon: type,
|
|
title: message,
|
|
timer: timer,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 확인 모달 표시
|
|
* @param {string} message - 확인 메시지
|
|
* @param {Function} onConfirm - 확인 시 콜백
|
|
* @param {Object} options - 추가 옵션
|
|
*/
|
|
function showConfirm(message, onConfirm, options = {}) {
|
|
const defaultOptions = {
|
|
title: options.title || '확인',
|
|
icon: options.icon || 'question',
|
|
confirmButtonText: options.confirmText || '확인',
|
|
cancelButtonText: options.cancelText || '취소',
|
|
showCancelButton: true,
|
|
reverseButtons: true,
|
|
};
|
|
|
|
SwalTailwind.fire({
|
|
...defaultOptions,
|
|
html: message,
|
|
}).then((result) => {
|
|
if (result.isConfirmed && typeof onConfirm === 'function') {
|
|
onConfirm();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 삭제 확인 모달 (위험 스타일)
|
|
* @param {string} itemName - 삭제할 항목명
|
|
* @param {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',
|
|
title: 'text-gray-900 font-semibold',
|
|
htmlContainer: 'text-gray-600',
|
|
confirmButton: 'bg-red-600 hover:bg-red-700 text-white font-medium px-5 py-2.5 rounded-lg transition-colors focus:ring-4 focus:ring-red-300',
|
|
cancelButton: 'bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium px-5 py-2.5 rounded-lg transition-colors focus:ring-4 focus:ring-gray-100',
|
|
actions: 'gap-3',
|
|
},
|
|
buttonsStyling: false,
|
|
}).then((result) => {
|
|
if (result.isConfirmed && typeof onConfirm === 'function') {
|
|
onConfirm();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 영구 삭제 확인 모달 (매우 위험)
|
|
* @param {string} itemName - 삭제할 항목명
|
|
* @param {Function} onConfirm - 확인 시 콜백
|
|
*/
|
|
function showPermanentDeleteConfirm(itemName, onConfirm) {
|
|
SwalTailwind.fire({
|
|
title: '⚠️ 영구 삭제',
|
|
html: `<span class="text-red-600 font-bold">"${itemName}"</span>을(를) 영구 삭제하시겠습니까?<br><br><span class="text-sm text-gray-500">이 작업은 되돌릴 수 없습니다!</span>`,
|
|
icon: 'error',
|
|
showCancelButton: true,
|
|
confirmButtonText: '영구 삭제',
|
|
cancelButtonText: '취소',
|
|
reverseButtons: true,
|
|
customClass: {
|
|
popup: 'rounded-xl shadow-2xl border-0',
|
|
title: 'text-red-600 font-bold',
|
|
htmlContainer: 'text-gray-600',
|
|
confirmButton: 'bg-red-600 hover:bg-red-700 text-white font-medium px-5 py-2.5 rounded-lg transition-colors focus:ring-4 focus:ring-red-300',
|
|
cancelButton: 'bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium px-5 py-2.5 rounded-lg transition-colors focus:ring-4 focus:ring-gray-100',
|
|
actions: 'gap-3',
|
|
},
|
|
buttonsStyling: false,
|
|
}).then((result) => {
|
|
if (result.isConfirmed && typeof onConfirm === 'function') {
|
|
onConfirm();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 성공 알림 모달
|
|
* @param {string} message - 메시지
|
|
* @param {Function} onClose - 닫기 후 콜백 (선택)
|
|
*/
|
|
function showSuccess(message, onClose = null) {
|
|
SwalTailwind.fire({
|
|
title: '완료',
|
|
text: message,
|
|
icon: 'success',
|
|
confirmButtonText: '확인',
|
|
}).then(() => {
|
|
if (typeof onClose === 'function') {
|
|
onClose();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 에러 알림 모달
|
|
* @param {string} message - 에러 메시지
|
|
*/
|
|
function showError(message) {
|
|
SwalTailwind.fire({
|
|
title: '오류',
|
|
text: message,
|
|
icon: 'error',
|
|
confirmButtonText: '확인',
|
|
});
|
|
}
|
|
</script>
|
|
|
|
@stack('scripts')
|
|
</body>
|
|
</html>
|