[FIX] 取貨碼操作紀錄顯示異常修正
1. 修正 index.blade.php 的 HTML 標籤嵌套錯誤,解決操作紀錄分頁空白問題。 2. 移除暫時的除錯訊息。 3. 整合 SystemOperationLog 機制於取貨碼模組。
This commit is contained in:
parent
84bf7de750
commit
820e1c87db
@ -10,6 +10,8 @@ use App\Models\Machine\Machine;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
use App\Models\System\SystemOperationLog;
|
||||
|
||||
class SalesController extends Controller
|
||||
{
|
||||
// 銷售&金流紀錄
|
||||
@ -30,6 +32,17 @@ class SalesController extends Controller
|
||||
// 取貨碼設定
|
||||
public function pickupCodes(Request $request)
|
||||
{
|
||||
$tab = $request->input('tab', 'list');
|
||||
$isAjax = $request->ajax();
|
||||
|
||||
$data = [
|
||||
'title' => '取貨碼設定',
|
||||
'description' => '產生與管理商品取貨驗證碼',
|
||||
'tab' => $tab,
|
||||
];
|
||||
|
||||
// 1. 取貨碼列表 (list)
|
||||
if (!$isAjax || $tab === 'list') {
|
||||
$query = PickupCode::with(['machine.slots.product', 'creator'])->latest();
|
||||
|
||||
if ($request->search) {
|
||||
@ -47,19 +60,32 @@ class SalesController extends Controller
|
||||
}
|
||||
|
||||
$per_page = $request->input('per_page', 15);
|
||||
$pickupCodes = $query->paginate($per_page)->withQueryString();
|
||||
$data['pickupCodes'] = $query->paginate($per_page, ['*'], 'list_page')->withQueryString();
|
||||
|
||||
// 供新增彈窗使用的機台清單
|
||||
$machines = Machine::all();
|
||||
$data['machines'] = Machine::all();
|
||||
}
|
||||
|
||||
return view('admin.sales.pickup-codes.index', [
|
||||
'title' => '取貨碼設定',
|
||||
'description' => '產生與管理商品取貨驗證碼',
|
||||
'pickupCodes' => $pickupCodes,
|
||||
'machines' => $machines,
|
||||
// 2. 操作紀錄 (logs)
|
||||
if (!$isAjax || $tab === 'logs') {
|
||||
$data['logs'] = SystemOperationLog::with('user:id,name')
|
||||
->where('module', 'pickup_code')
|
||||
->latest()
|
||||
->paginate($request->input('per_page', 15), ['*'], 'log_page')
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
if ($isAjax) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
'html' => view('admin.sales.pickup-codes.partials.tab-' . $tab, $data)->render()
|
||||
]);
|
||||
}
|
||||
|
||||
return view('admin.sales.pickup-codes.index', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 產生取貨碼
|
||||
*/
|
||||
@ -85,6 +111,16 @@ class SalesController extends Controller
|
||||
'created_by' => Auth::id(),
|
||||
]);
|
||||
|
||||
SystemOperationLog::create([
|
||||
'company_id' => $machine->company_id,
|
||||
'user_id' => Auth::id(),
|
||||
'module' => 'pickup_code',
|
||||
'action' => 'create',
|
||||
'target_id' => $pickupCode->id,
|
||||
'target_type' => PickupCode::class,
|
||||
'new_values' => $pickupCode->toArray(),
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Pickup code generated: :code', ['code' => $pickupCode->code]));
|
||||
}
|
||||
|
||||
@ -97,10 +133,23 @@ class SalesController extends Controller
|
||||
'expires_at' => 'required|date|after:now',
|
||||
]);
|
||||
|
||||
$oldValues = $pickupCode->toArray();
|
||||
|
||||
$pickupCode->update([
|
||||
'expires_at' => $validated['expires_at'],
|
||||
]);
|
||||
|
||||
SystemOperationLog::create([
|
||||
'company_id' => $pickupCode->company_id,
|
||||
'user_id' => Auth::id(),
|
||||
'module' => 'pickup_code',
|
||||
'action' => 'update',
|
||||
'target_id' => $pickupCode->id,
|
||||
'target_type' => PickupCode::class,
|
||||
'old_values' => $oldValues,
|
||||
'new_values' => $pickupCode->toArray(),
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Pickup code updated.'));
|
||||
}
|
||||
|
||||
@ -109,7 +158,20 @@ class SalesController extends Controller
|
||||
*/
|
||||
public function destroyPickupCode(PickupCode $pickupCode)
|
||||
{
|
||||
$oldValues = $pickupCode->toArray();
|
||||
$pickupCode->update(['status' => 'cancelled']);
|
||||
|
||||
SystemOperationLog::create([
|
||||
'company_id' => $pickupCode->company_id,
|
||||
'user_id' => Auth::id(),
|
||||
'module' => 'pickup_code',
|
||||
'action' => 'cancel',
|
||||
'target_id' => $pickupCode->id,
|
||||
'target_type' => PickupCode::class,
|
||||
'old_values' => $oldValues,
|
||||
'new_values' => $pickupCode->toArray(),
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Pickup code cancelled.'));
|
||||
}
|
||||
|
||||
|
||||
37
app/Models/System/SystemOperationLog.php
Normal file
37
app/Models/System/SystemOperationLog.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\System;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Traits\TenantScoped;
|
||||
use App\Models\User;
|
||||
|
||||
class SystemOperationLog extends Model
|
||||
{
|
||||
use HasFactory, TenantScoped;
|
||||
|
||||
protected $fillable = [
|
||||
'company_id',
|
||||
'user_id',
|
||||
'module',
|
||||
'action',
|
||||
'target_id',
|
||||
'target_type',
|
||||
'old_values',
|
||||
'new_values',
|
||||
'note',
|
||||
'ip_address',
|
||||
'user_agent',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'old_values' => 'array',
|
||||
'new_values' => 'array',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('system_operation_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('company_id')->nullable()->constrained()->onDelete('cascade');
|
||||
$table->foreignId('user_id')->nullable()->constrained()->onDelete('set null');
|
||||
$table->string('module', 50)->index();
|
||||
$table->string('action', 50);
|
||||
$table->unsignedBigInteger('target_id')->nullable()->index();
|
||||
$table->string('target_type', 100)->nullable();
|
||||
$table->json('old_values')->nullable();
|
||||
$table->json('new_values')->nullable();
|
||||
$table->text('note')->nullable();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('system_operation_logs');
|
||||
}
|
||||
};
|
||||
@ -6,30 +6,13 @@ $slotSelectConfig = [
|
||||
"hasSearch" => true,
|
||||
"searchPlaceholder" => __("Search Slot..."),
|
||||
"isHidePlaceholder" => false,
|
||||
"searchClasses" => "block w-[calc(100%-16px)] mx-2 py-2 px-3 text-sm border-slate-200 dark:border-white/10 rounded-lg
|
||||
focus:border-cyan-500 focus:ring-cyan-500 bg-slate-50 dark:bg-slate-900/50 dark:text-slate-200
|
||||
placeholder:text-slate-400 dark:placeholder:text-slate-500",
|
||||
"searchClasses" => "block w-[calc(100%-16px)] mx-2 py-2 px-3 text-sm border-slate-200 dark:border-white/10 rounded-lg focus:border-cyan-500 focus:ring-cyan-500 bg-slate-50 dark:bg-slate-900/50 dark:text-slate-200 placeholder:text-slate-400 dark:placeholder:text-slate-500",
|
||||
"searchWrapperClasses" => "sticky top-0 bg-white/95 dark:bg-slate-900/95 backdrop-blur-md p-2 z-10",
|
||||
"toggleClasses" => "hs-select-toggle luxury-select-toggle",
|
||||
"toggleTemplate" => '<button type="button" aria-expanded="false"><span class="me-2" data-icon></span><span
|
||||
class="text-slate-800 dark:text-slate-200" data-title></span>
|
||||
<div class="ms-auto"><svg class="size-4 text-slate-400 transition-transform duration-300"
|
||||
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m6 9 6 6 6-6" />
|
||||
</svg></div>
|
||||
</button>',
|
||||
"dropdownClasses" => "hs-select-menu w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-white/10
|
||||
rounded-xl shadow-[0_20px_50px_rgba(0,0,0,0.3)] mt-2 z-[150] animate-luxury-in",
|
||||
"optionClasses" => "hs-select-option py-2.5 px-3 mb-0.5 text-sm text-slate-800 dark:text-slate-300 cursor-pointer
|
||||
hover:bg-slate-100 dark:hover:bg-cyan-500/10 dark:hover:text-cyan-400 rounded-lg flex items-center justify-between
|
||||
transition-all duration-300",
|
||||
"optionTemplate" => '<div class="flex items-center justify-between w-full"><span data-title></span><span
|
||||
class="hs-select-active-indicator hidden text-cyan-500"><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg></span></div>'
|
||||
"toggleTemplate" => '<button type="button" aria-expanded="false"><span class="me-2" data-icon></span><span class="text-slate-800 dark:text-slate-200" data-title></span><div class="ms-auto"><svg class="size-4 text-slate-400 transition-transform duration-300" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg></div></button>',
|
||||
"dropdownClasses" => "hs-select-menu w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-white/10 rounded-xl shadow-[0_20px_50px_rgba(0,0,0,0.3)] mt-2 z-[150] animate-luxury-in",
|
||||
"optionClasses" => "hs-select-option py-2.5 px-3 mb-0.5 text-sm text-slate-800 dark:text-slate-300 cursor-pointer hover:bg-slate-100 dark:hover:bg-cyan-500/10 dark:hover:text-cyan-400 rounded-lg flex items-center justify-between transition-all duration-300",
|
||||
"optionTemplate" => '<div class="flex items-center justify-between w-full"><span data-title></span><span class="hs-select-active-indicator hidden text-cyan-500"><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg></span></div>'
|
||||
];
|
||||
@endphp
|
||||
|
||||
@ -50,11 +33,26 @@ transition-all duration-300",
|
||||
</button>
|
||||
</x-page-header>
|
||||
|
||||
{{-- Tab Navigation --}}
|
||||
<div class="mb-4">
|
||||
<x-tab-nav model="activeTab">
|
||||
<x-tab-nav-item value="list" :label="__('Pickup Codes')" model="activeTab" />
|
||||
<x-tab-nav-item value="logs" :label="__('Operation Records')" model="activeTab" />
|
||||
</x-tab-nav>
|
||||
</div>
|
||||
|
||||
{{-- Content Containers --}}
|
||||
<div class="relative min-h-[400px]">
|
||||
{{-- List Tab --}}
|
||||
<div x-show="activeTab === 'list'" x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 translate-y-4" x-transition:enter-end="opacity-100 translate-y-0"
|
||||
style="display: none;">
|
||||
|
||||
{{-- Main Content Card --}}
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in relative overflow-hidden">
|
||||
<x-luxury-spinner show="isLoadingTable" />
|
||||
|
||||
<div id="ajax-content-container"
|
||||
<div id="tab-list-container"
|
||||
:class="{ 'opacity-30 pointer-events-none transition-opacity duration-300': isLoadingTable }"
|
||||
@ajax:navigate.prevent.stop="fetchPage($event.detail.url)"
|
||||
@click="if ($event.target.closest('a') && $event.target.closest('a').href && ($event.target.closest('a').href.includes('page=') || $event.target.closest('a').href.includes('per_page='))) { $event.preventDefault(); fetchPage($event.target.closest('a').href); }">
|
||||
@ -351,7 +349,26 @@ transition-all duration-300",
|
||||
<div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
|
||||
{{ $pickupCodes->links('vendor.pagination.luxury') }}
|
||||
</div>
|
||||
</div> {{-- End tab-list-container --}}
|
||||
</div> {{-- End luxury-card --}}
|
||||
</div>
|
||||
{{-- End List Tab --}}
|
||||
|
||||
{{-- Logs Tab --}}
|
||||
<div x-show="activeTab === 'logs'" class="relative"
|
||||
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4"
|
||||
x-transition:enter-end="opacity-100 translate-y-0" style="display: none;">
|
||||
|
||||
{{-- Main Content Card --}}
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in relative overflow-hidden">
|
||||
<x-luxury-spinner show="tabLoading === 'logs'" />
|
||||
|
||||
<div id="tab-logs-container" class="relative">
|
||||
@include('admin.sales.pickup-codes.partials.tab-logs')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{-- End Logs Tab --}}
|
||||
</div>
|
||||
|
||||
{{-- Create Modal --}}
|
||||
@ -582,6 +599,8 @@ transition-all duration-300",
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('pickupCodeManager', (config) => ({
|
||||
activeTab: '{{ request("tab", "list") }}',
|
||||
tabLoading: null,
|
||||
showCreateModal: false,
|
||||
selectedMachine: '',
|
||||
selectedSlot: '',
|
||||
@ -598,6 +617,48 @@ transition-all duration-300",
|
||||
slotSelectConfig: config,
|
||||
customCode: '',
|
||||
|
||||
|
||||
|
||||
async fetchTabData(tab, containerSelector) {
|
||||
if (this.tabLoading === tab) return;
|
||||
this.tabLoading = tab;
|
||||
|
||||
const container = document.querySelector(containerSelector);
|
||||
let url = '{{ route("admin.sales.pickup-codes") }}';
|
||||
|
||||
const urlObj = new URL(url, window.location.origin);
|
||||
urlObj.searchParams.set('tab', tab);
|
||||
urlObj.searchParams.set('_ajax', '1');
|
||||
url = urlObj.toString();
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
if (container) {
|
||||
container.innerHTML = data.html;
|
||||
this.$nextTick(() => {
|
||||
if (window.HSStaticMethods) window.HSStaticMethods.autoInit();
|
||||
});
|
||||
}
|
||||
|
||||
const historyUrl = new URL(url, window.location.origin);
|
||||
historyUrl.searchParams.delete('_ajax');
|
||||
window.history.pushState({}, '', historyUrl.toString());
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
window.showToast?.('{{ __("Loading failed") }}', 'error');
|
||||
} finally {
|
||||
this.tabLoading = null;
|
||||
}
|
||||
},
|
||||
|
||||
generateRandomCode() {
|
||||
this.customCode = Math.floor(Math.random() * 90000000 + 10000000).toString();
|
||||
},
|
||||
@ -668,14 +729,14 @@ transition-all duration-300",
|
||||
copyQrLink() {
|
||||
if (!this.activeTicketUrl) return;
|
||||
navigator.clipboard.writeText(this.activeTicketUrl).then(() => {
|
||||
window.showToast('{{ __('Link Copied') }}', 'success');
|
||||
window.showToast("{{ __('Link Copied') }}", 'success');
|
||||
});
|
||||
},
|
||||
|
||||
copyQrCode() {
|
||||
if (!this.activeQrCode) return;
|
||||
navigator.clipboard.writeText(this.activeQrCode).then(() => {
|
||||
window.showToast('{{ __('Code Copied') }}', 'success');
|
||||
window.showToast("{{ __('Code Copied') }}", 'success');
|
||||
});
|
||||
},
|
||||
|
||||
@ -773,6 +834,22 @@ transition-all duration-300",
|
||||
|
||||
init() {
|
||||
this.generateRandomCode();
|
||||
|
||||
// Tab 切換時更新 URL(同庫存管理模式)
|
||||
this.$watch('activeTab', (newTab) => {
|
||||
const url = new URL(window.location.origin + window.location.pathname);
|
||||
url.searchParams.set('tab', newTab);
|
||||
window.history.pushState({}, '', url);
|
||||
|
||||
if (newTab === 'logs') {
|
||||
this.fetchTabData('logs', '#tab-logs-container');
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (window.HSStaticMethods) window.HSStaticMethods.autoInit();
|
||||
});
|
||||
});
|
||||
|
||||
this.$watch('slots', () => this.updateSlotSelect());
|
||||
this.$watch('selectedMachine', (val) => {
|
||||
this.syncSelect('modal-pickup-machine', val);
|
||||
|
||||
@ -0,0 +1,98 @@
|
||||
<div class="overflow-hidden bg-white/80 dark:bg-slate-900/80 backdrop-blur-xl border border-slate-200 dark:border-slate-800 shadow-sm rounded-xl">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-800">
|
||||
<thead class="bg-slate-50/50 dark:bg-slate-800/50">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider">
|
||||
{{ __('Time') }}
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider">
|
||||
{{ __('Operator') }}
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider">
|
||||
{{ __('Actions') }}
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider">
|
||||
{{ __('Target') }}
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider">
|
||||
{{ __('Notes') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200 dark:divide-slate-800">
|
||||
@forelse($logs as $log)
|
||||
<tr class="hover:bg-slate-50/50 dark:hover:bg-slate-800/50 transition-colors duration-200">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-600 dark:text-slate-300">
|
||||
{{ $log->created_at->format('Y-m-d H:i:s') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-6 h-6 rounded-full bg-indigo-100 dark:bg-indigo-900/50 flex items-center justify-center text-indigo-600 dark:text-indigo-400 text-xs font-medium">
|
||||
{{ Str::substr($log->user->name ?? 'Sys', 0, 1) }}
|
||||
</div>
|
||||
<span class="text-sm font-medium text-slate-700 dark:text-slate-200">
|
||||
{{ $log->user->name ?? __('System') }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
||||
@if($log->action === 'create')
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-emerald-50 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-800/50">
|
||||
{{ __('Generate') }}
|
||||
</span>
|
||||
@elseif($log->action === 'update')
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-blue-50 text-blue-700 dark:bg-blue-500/10 dark:text-blue-400 border border-blue-200 dark:border-blue-800/50">
|
||||
{{ __('Update') }}
|
||||
</span>
|
||||
@elseif($log->action === 'cancel')
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-rose-50 text-rose-700 dark:bg-rose-500/10 dark:text-rose-400 border border-rose-200 dark:border-rose-800/50">
|
||||
{{ __('Cancel') }}
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-slate-50 text-slate-700 dark:bg-slate-500/10 dark:text-slate-400 border border-slate-200 dark:border-slate-800/50">
|
||||
{{ $log->action }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-600 dark:text-slate-300">
|
||||
ID: {{ $log->target_id }}
|
||||
@if($log->new_values && isset($log->new_values['code']))
|
||||
<span class="ml-2 px-2 py-0.5 rounded text-xs bg-slate-100 dark:bg-slate-800 font-mono">
|
||||
{{ $log->new_values['code'] }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-500 dark:text-slate-400">
|
||||
@if($log->action === 'update' && isset($log->old_values['expires_at']) && isset($log->new_values['expires_at']))
|
||||
<div>{{ __('Expires At') }}:
|
||||
<span class="line-through opacity-70">{{ \Carbon\Carbon::parse($log->old_values['expires_at'])->format('Y-m-d H:i') }}</span>
|
||||
<svg class="inline w-3 h-3 mx-1 text-slate-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" /></svg>
|
||||
<span class="text-amber-600 dark:text-amber-400 font-medium">{{ \Carbon\Carbon::parse($log->new_values['expires_at'])->format('Y-m-d H:i') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="px-6 py-12 text-center">
|
||||
<div class="flex flex-col items-center justify-center text-slate-400 dark:text-slate-500">
|
||||
<svg class="w-12 h-12 mb-4 opacity-50" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
|
||||
</svg>
|
||||
<p class="text-sm font-medium">{{ __('No records found') }}</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
@if(isset($logs) && $logs instanceof \Illuminate\Pagination\LengthAwarePaginator && $logs->hasPages())
|
||||
<div class="px-6 py-4 border-t border-slate-200 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-800/50">
|
||||
{{ $logs->links('vendor.pagination.luxury') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@ -19,30 +19,13 @@ $config = [
|
||||
"hasSearch" => (bool)$hasSearch,
|
||||
"searchPlaceholder" => $placeholder ?: __('Search...'),
|
||||
"isHidePlaceholder" => false,
|
||||
"searchClasses" => "block w-[calc(100%-16px)] mx-2 py-2 px-3 text-sm border-slate-200 dark:border-white/10 rounded-lg
|
||||
focus:border-cyan-500 focus:ring-cyan-500 bg-slate-50 dark:bg-slate-900/50 dark:text-slate-200
|
||||
placeholder:text-slate-400 dark:placeholder:text-slate-500",
|
||||
"searchClasses" => "block w-[calc(100%-16px)] mx-2 py-2 px-3 text-sm border-slate-200 dark:border-white/10 rounded-lg focus:border-cyan-500 focus:ring-cyan-500 bg-slate-50 dark:bg-slate-900/50 dark:text-slate-200 placeholder:text-slate-400 dark:placeholder:text-slate-500",
|
||||
"searchWrapperClasses" => "sticky top-0 bg-white/95 dark:bg-slate-900/95 backdrop-blur-md p-2 z-10",
|
||||
"toggleClasses" => "hs-select-toggle luxury-select-toggle",
|
||||
"toggleTemplate" => '<button type="button" aria-expanded="false"><span class="me-2" data-icon></span><span
|
||||
class="text-slate-800 dark:text-slate-200" data-title></span>
|
||||
<div class="ms-auto"><svg class="size-4 text-slate-400 transition-transform duration-300"
|
||||
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m6 9 6 6 6-6" />
|
||||
</svg></div>
|
||||
</button>',
|
||||
"dropdownClasses" => "hs-select-menu w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-white/10
|
||||
rounded-xl shadow-[0_20px_50px_rgba(0,0,0,0.3)] mt-2 z-[150] animate-luxury-in",
|
||||
"optionClasses" => "hs-select-option py-2.5 px-3 mb-0.5 text-sm text-slate-800 dark:text-slate-300 cursor-pointer
|
||||
hover:bg-slate-100 dark:hover:bg-cyan-500/10 dark:hover:text-cyan-400 rounded-lg flex items-center justify-between
|
||||
transition-all duration-300",
|
||||
"optionTemplate" => '<div class="flex items-center justify-between w-full"><span data-title></span><span
|
||||
class="hs-select-active-indicator hidden text-cyan-500"><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg></span></div>'
|
||||
"toggleTemplate" => '<button type="button" aria-expanded="false"><span class="me-2" data-icon></span><span class="text-slate-800 dark:text-slate-200" data-title></span><div class="ms-auto"><svg class="size-4 text-slate-400 transition-transform duration-300" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg></div></button>',
|
||||
"dropdownClasses" => "hs-select-menu w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-white/10 rounded-xl shadow-[0_20px_50px_rgba(0,0,0,0.3)] mt-2 z-[150] animate-luxury-in",
|
||||
"optionClasses" => "hs-select-option py-2.5 px-3 mb-0.5 text-sm text-slate-800 dark:text-slate-300 cursor-pointer hover:bg-slate-100 dark:hover:bg-cyan-500/10 dark:hover:text-cyan-400 rounded-lg flex items-center justify-between transition-all duration-300",
|
||||
"optionTemplate" => '<div class="flex items-center justify-between w-full"><span data-title></span><span class="hs-select-active-indicator hidden text-cyan-500"><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg></span></div>'
|
||||
];
|
||||
@endphp
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user