feature: keep sidebar and add service layer

This commit is contained in:
antv
2026-06-30 11:59:34 +07:00
parent 8a77324f89
commit c5016489ac
54 changed files with 1181 additions and 284 deletions
@@ -0,0 +1,32 @@
@props(['id', 'title' => 'Xác nhận', 'message' => '', 'confirmText' => 'Đồng ý', 'cancelText' => 'Hủy', 'type' => 'danger'])
<div id="{{ $id }}" class="fixed inset-0 z-[100] hidden flex-col items-center justify-center outline-none opacity-0 transition-opacity duration-300" aria-labelledby="modal-title-{{ $id }}" role="dialog" aria-modal="true">
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm cursor-pointer" onclick="closeModal('{{ $id }}')"></div>
<div class="modal-panel bg-white rounded-2xl shadow-2xl w-full sm:max-w-md overflow-hidden z-50 relative transform scale-95 translate-y-4 transition-all duration-300 m-4 flex flex-col">
<div class="p-6 flex flex-col items-center text-center">
<!-- Icon based on type -->
<div class="w-14 h-14 rounded-full flex items-center justify-center mb-4 {{ $type === 'danger' ? 'bg-red-50 text-red-500' : 'bg-blue-50 text-blue-500' }}">
@if($type === 'danger')
<svg class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" 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>
@else
<svg class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
@endif
</div>
<h3 id="modal-title-{{ $id }}" class="text-xl font-bold text-slate-800 mb-2">{{ $title }}</h3>
<p class="text-slate-500 text-sm leading-relaxed">{{ $message }}</p>
</div>
<div class="px-6 py-4 bg-slate-50 flex gap-3 border-t border-slate-100 justify-end">
<button type="button" onclick="closeModal('{{ $id }}')" class="px-4 py-2 bg-white hover:bg-slate-100 border border-slate-200 text-slate-700 text-sm font-semibold rounded-lg transition-all duration-200 cursor-pointer">
{{ $cancelText }}
</button>
<button type="button" onclick="closeModal('{{ $id }}')" class="px-4 py-2 text-white text-sm font-semibold rounded-lg transition-all duration-200 cursor-pointer {{ $type === 'danger' ? 'bg-red-600 hover:bg-red-700' : 'bg-primary hover:bg-primary-hover' }}">
{{ $confirmText }}
</button>
</div>
</div>
</div>
@@ -0,0 +1,17 @@
@props(['title' => 'Không có dữ liệu', 'message' => 'Không tìm thấy dữ liệu nào phù hợp với yêu cầu.', 'actionText' => '', 'actionId' => ''])
<div class="flex flex-col items-center justify-center p-8 bg-white border border-slate-100 rounded-2xl shadow-sm text-center max-w-lg mx-auto my-6">
<div class="w-48 h-48 mb-6 flex items-center justify-center text-slate-300">
<!-- SVG illustration of empty search/mailbox -->
<svg class="w-32 h-32 text-slate-300 animate-pulse" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5m6 4.125l2.25 2.25m0 0l2.25 2.25M12 13.875l2.25-2.25M12 13.875l-2.25-2.25M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" />
</svg>
</div>
<h3 class="text-lg font-bold text-slate-800 mb-2">{{ $title }}</h3>
<p class="text-sm text-slate-500 max-w-sm mb-6 leading-relaxed">{{ $message }}</p>
@if($actionText)
<button type="button" @if($actionId) id="{{ $actionId }}" @endif class="btn-primary px-6 py-2 text-sm font-semibold rounded-lg shadow-sm cursor-pointer transition-all duration-200">
{{ $actionText }}
</button>
@endif
</div>
@@ -0,0 +1,22 @@
@props(['id', 'title' => 'Đã xảy ra lỗi', 'message' => '', 'buttonText' => 'Đóng'])
<div id="{{ $id }}" class="fixed inset-0 z-[100] hidden flex-col items-center justify-center outline-none opacity-0 transition-opacity duration-300" aria-labelledby="modal-title-{{ $id }}" role="dialog" aria-modal="true">
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm cursor-pointer" onclick="closeModal('{{ $id }}')"></div>
<div class="modal-panel bg-white rounded-2xl shadow-2xl w-full sm:max-w-md overflow-hidden z-50 relative transform scale-95 translate-y-4 transition-all duration-300 m-4 flex flex-col">
<div class="p-6 flex flex-col items-center text-center">
<div class="w-14 h-14 rounded-full bg-red-50 text-red-500 flex items-center justify-center mb-4 animate-bounce">
<svg class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
<h3 id="modal-title-{{ $id }}" class="text-xl font-bold text-slate-800 mb-2">{{ $title }}</h3>
<p class="text-slate-500 text-sm leading-relaxed">{{ $message }}</p>
</div>
<div class="px-6 py-4 bg-slate-50 flex justify-center border-t border-slate-100">
<button type="button" onclick="closeModal('{{ $id }}')" class="w-full sm:w-auto px-6 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-semibold rounded-lg shadow-sm transition-all duration-200 cursor-pointer text-center">
{{ $buttonText }}
</button>
</div>
</div>
</div>
@@ -0,0 +1,22 @@
@props(['id', 'title' => 'Thông tin', 'message' => '', 'buttonText' => 'Đóng'])
<div id="{{ $id }}" class="fixed inset-0 z-[100] hidden flex-col items-center justify-center outline-none opacity-0 transition-opacity duration-300" aria-labelledby="modal-title-{{ $id }}" role="dialog" aria-modal="true">
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm cursor-pointer" onclick="closeModal('{{ $id }}')"></div>
<div class="modal-panel bg-white rounded-2xl shadow-2xl w-full sm:max-w-md overflow-hidden z-50 relative transform scale-95 translate-y-4 transition-all duration-300 m-4 flex flex-col">
<div class="p-6 flex flex-col items-center text-center">
<div class="w-14 h-14 rounded-full bg-blue-50 text-blue-500 flex items-center justify-center mb-4">
<svg class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 id="modal-title-{{ $id }}" class="text-xl font-bold text-slate-800 mb-2">{{ $title }}</h3>
<p class="text-slate-500 text-sm leading-relaxed">{{ $message }}</p>
</div>
<div class="px-6 py-4 bg-slate-50 flex justify-center border-t border-slate-100">
<button type="button" onclick="closeModal('{{ $id }}')" class="w-full sm:w-auto px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-semibold rounded-lg shadow-sm transition-all duration-200 cursor-pointer text-center">
{{ $buttonText }}
</button>
</div>
</div>
</div>
@@ -0,0 +1,22 @@
@props(['id', 'title' => 'Thành công', 'message' => '', 'buttonText' => 'Xác nhận'])
<div id="{{ $id }}" class="fixed inset-0 z-[100] hidden flex-col items-center justify-center outline-none opacity-0 transition-opacity duration-300" aria-labelledby="modal-title-{{ $id }}" role="dialog" aria-modal="true">
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm cursor-pointer" onclick="closeModal('{{ $id }}')"></div>
<div class="modal-panel bg-white rounded-2xl shadow-2xl w-full sm:max-w-md overflow-hidden z-50 relative transform scale-95 translate-y-4 transition-all duration-300 m-4 flex flex-col">
<div class="p-6 flex flex-col items-center text-center">
<div class="w-14 h-14 rounded-full bg-emerald-50 text-emerald-500 flex items-center justify-center mb-4 scale-110">
<svg class="w-8 h-8 animate-pulse" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
</div>
<h3 id="modal-title-{{ $id }}" class="text-xl font-bold text-slate-800 mb-2">{{ $title }}</h3>
<p class="text-slate-500 text-sm leading-relaxed">{{ $message }}</p>
</div>
<div class="px-6 py-4 bg-slate-50 flex justify-center border-t border-slate-100">
<button type="button" onclick="closeModal('{{ $id }}')" class="w-full sm:w-auto px-6 py-2 bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-semibold rounded-lg shadow-sm transition-all duration-200 cursor-pointer text-center">
{{ $buttonText }}
</button>
</div>
</div>
</div>
@@ -0,0 +1,20 @@
@props(['id' => 'toast', 'type' => 'success', 'message' => ''])
<div id="{{ $id }}" class="fixed top-4 right-4 z-[999] flex items-center w-full max-w-sm p-4 space-x-3 text-slate-500 bg-white rounded-xl shadow-xl border border-slate-100 hidden opacity-0 transform translate-y-[-10px] transition-all duration-300" role="alert">
<div class="inline-flex items-center justify-center shrink-0 w-9 h-9 rounded-lg {{ $type === 'success' ? 'bg-emerald-50 text-emerald-500' : ($type === 'error' ? 'bg-red-50 text-red-500' : ($type === 'warning' ? 'bg-amber-50 text-amber-500' : 'bg-blue-50 text-blue-500')) }}">
@if($type === 'success')
<svg class="w-5.5 h-5.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg>
@elseif($type === 'error')
<svg class="w-5.5 h-5.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
@elseif($type === 'warning')
<svg class="w-5.5 h-5.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
@else
<svg class="w-5.5 h-5.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
@endif
</div>
<div class="text-sm font-semibold text-slate-800 flex-1">{{ $message }}</div>
<button type="button" onclick="closeToast('{{ $id }}')" class="text-slate-400 hover:text-slate-600 rounded-lg p-1 hover:bg-slate-50 inline-flex h-8 w-8 justify-center items-center cursor-pointer transition-colors">
<span class="sr-only">Close</span>
<svg class="w-4 h-4" fill="none" viewBox="0 0 14 14" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/></svg>
</button>
</div>
@@ -0,0 +1,22 @@
@props(['id', 'title' => 'Cảnh báo', 'message' => '', 'buttonText' => 'Đã hiểu'])
<div id="{{ $id }}" class="fixed inset-0 z-[100] hidden flex-col items-center justify-center outline-none opacity-0 transition-opacity duration-300" aria-labelledby="modal-title-{{ $id }}" role="dialog" aria-modal="true">
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm cursor-pointer" onclick="closeModal('{{ $id }}')"></div>
<div class="modal-panel bg-white rounded-2xl shadow-2xl w-full sm:max-w-md overflow-hidden z-50 relative transform scale-95 translate-y-4 transition-all duration-300 m-4 flex flex-col">
<div class="p-6 flex flex-col items-center text-center">
<div class="w-14 h-14 rounded-full bg-amber-50 text-amber-500 flex items-center justify-center mb-4">
<svg class="w-8 h-8 animate-bounce" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<h3 id="modal-title-{{ $id }}" class="text-xl font-bold text-slate-800 mb-2">{{ $title }}</h3>
<p class="text-slate-500 text-sm leading-relaxed">{{ $message }}</p>
</div>
<div class="px-6 py-4 bg-slate-50 flex justify-center border-t border-slate-100">
<button type="button" onclick="closeModal('{{ $id }}')" class="w-full sm:w-auto px-6 py-2 bg-amber-500 hover:bg-amber-600 text-white text-sm font-semibold rounded-lg shadow-sm transition-all duration-200 cursor-pointer text-center">
{{ $buttonText }}
</button>
</div>
</div>
</div>