diff --git a/app/Http/Controllers/Admin/SalesController.php b/app/Http/Controllers/Admin/SalesController.php index 5bce89b..26b677e 100644 --- a/app/Http/Controllers/Admin/SalesController.php +++ b/app/Http/Controllers/Admin/SalesController.php @@ -211,10 +211,17 @@ class SalesController extends Controller $validated = $request->validate([ 'name' => 'nullable|string|max:50', 'expires_at' => 'nullable|date', - 'status' => 'required|in:active,disabled', + 'status' => 'nullable|in:active,disabled', ]); - $passCode->update($validated); + // 確保 expires_at 為空字串時轉為 null + if (isset($validated['expires_at']) && empty($validated['expires_at'])) { + $validated['expires_at'] = null; + } + + $passCode->update(array_filter($validated, function ($value, $key) use ($request) { + return $request->has($key); + }, ARRAY_FILTER_USE_BOTH)); return back()->with('success', __('Pass code updated.')); } @@ -225,7 +232,7 @@ class SalesController extends Controller public function destroyPassCode(PassCode $passCode) { $passCode->update(['status' => 'disabled']); - return back()->with('success', __('Pass code disabled.')); + return back()->with('success', __('Pass code cancelled.')); } // 來店禮設定 diff --git a/lang/zh_TW.json b/lang/zh_TW.json index eb0e56d..e54c601 100644 --- a/lang/zh_TW.json +++ b/lang/zh_TW.json @@ -931,7 +931,8 @@ "Pass Codes": "通行碼", "Pass code created: :code": "通行碼已建立::code", "Pass code deleted.": "通行碼已刪除", - "Pass code disabled.": "通行碼已停用。", + "Pass code cancelled.": "通行碼已取消。", + "Pass code disabled.": "通行碼已取消。", "Pass code updated.": "通行碼已更新", "Password": "密碼", "Password updated successfully.": "密碼已成功變更。", diff --git a/resources/views/admin/sales/pass-codes/index.blade.php b/resources/views/admin/sales/pass-codes/index.blade.php index 1aa6da8..8a2bea1 100644 --- a/resources/views/admin/sales/pass-codes/index.blade.php +++ b/resources/views/admin/sales/pass-codes/index.blade.php @@ -3,6 +3,7 @@ @section('content')
+ x-model="status" @change="$el.closest('form').dispatchEvent(new Event('submit'))">
- @php - $displayStatusMobile = $code->status === 'active' && $code->expires_at?->isPast() ? 'expired' : $code->status; - @endphp - -
+ @php + $displayStatusMobile = $code->status === 'active' && $code->expires_at?->isPast() ? + 'expired' : ($code->status === 'disabled' ? 'cancelled' : $code->status); + @endphp + + {{-- Info Grid --}} @@ -380,14 +415,28 @@ {{-- Action Buttons --}} -
+
@if($code->status === 'active' && (!$code->expires_at || $code->expires_at->isFuture())) + @@ -395,8 +444,7 @@ @if($code->status === 'active')
+ action="{{ route('admin.sales.pass-codes.destroy', $code) }}" method="POST" class="flex-1"> @csrf @method('DELETE')
- + @csrf
@@ -498,13 +547,16 @@
- - -
+ + +
@@ -512,11 +564,14 @@ -
{{ __('Days') }}
+
+ {{ __('Days') }}
@@ -524,18 +579,28 @@
- {{ __('Estimated Expiry') }} + {{ + __('Estimated Expiry') }}
-
- - +
+ + - +
-

{{ __('Leave empty or 0 for permanent code') }}

+

+ {{ __('Leave empty or 0 for permanent code') }}

@@ -554,8 +619,8 @@ {{-- Confirm Cancel Modal --}} + :message="__('Are you sure you want to cancel this pass code? This action cannot be undone.')" + :confirmText="__('Yes, Cancel')" confirmAction="submitDelete()" confirmColor="rose" iconType="danger" /> {{-- QR Code Modal --}}
- +
-

{{ __('Pass Code QR') }}

-

+

{{ + __('Pass Code QR') }}

+

@@ -613,7 +681,8 @@ class="flex-1 py-3 px-4 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-slate-100 dark:border-slate-700 hover:border-cyan-500/20 transition-all flex items-center justify-center gap-2 group" title="{{ __('Copy Code') }}"> - + {{ __('Copy') }} @@ -632,12 +701,74 @@
-
- +
+
+ + {{-- Edit Modal --}} +
+
+
+ +
+
+

{{ + __('Edit Pass Code') }}

+ +
+ + + @csrf + @method('PATCH') + +
+ + +
+ +
+ + +

{{ __('Leave + empty for permanent code') }}

+
+ +
+ + +
+ +
+
+
@endsection diff --git a/resources/views/admin/sales/pickup-codes/index.blade.php b/resources/views/admin/sales/pickup-codes/index.blade.php index 53ebc62..4bc3c71 100644 --- a/resources/views/admin/sales/pickup-codes/index.blade.php +++ b/resources/views/admin/sales/pickup-codes/index.blade.php @@ -6,8 +6,10 @@ $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", - "searchWrapperClasses" => "sticky top-0 bg-white/95 dark:bg-slate-900/95 backdrop-blur-md p-2 z-10", +"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" => '
', - "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", +"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" => '
-
- {{ $code->expires_at->format('Y-m-d H:i') }}
-
{{ - $code->expires_at->diffForHumans() }}
+
+ {{ $code->expires_at->format('Y-m-d H:i') }} +
+
+ {{ $code->expires_at->diffForHumans() }} +
@php @@ -290,9 +296,12 @@ $slotSelectConfig = [

- {{ __('Expires At') }}

-

- {{ $code->expires_at->format('Y-m-d H:i') }}

+ {{ __('Expiry') }}

+

+ {{ $code->expires_at->format('m-d H:i') }} + {{ $code->expires_at->diffForHumans() + }} +

@@ -372,7 +381,8 @@ $slotSelectConfig = [ -
+ @csrf
@@ -422,57 +432,46 @@ $slotSelectConfig = [ maxlength="12" required>
- {{-- Validity Period Section --}} -
+ {{-- Validity Period Section (Compact) --}} +
+ +
-
-
-
- + class="bg-slate-50 dark:bg-slate-800/40 rounded-[2rem] p-6 border border-slate-100 dark:border-slate-800/50 shadow-sm"> +
+
+ +
+
+ + {{ + __('Hrs') }}
- {{ __('Max 24 Hours') }}
-
- {{-- Slider and Value --}} -
-
- -
-
- - {{ - __('Hrs') }} -
+
+
+ {{ + __('Expected Expiry') }} + {{ __('Max 24 + Hours') }}
- - {{-- Result Display --}} -
-
- {{ - __('Expected Expiry Date & Time') }} -
- - - - -
-
+
+ + + +
@@ -546,7 +545,8 @@ $slotSelectConfig = [ class="flex-1 py-3 px-4 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-slate-100 dark:border-slate-700 hover:border-cyan-500/20 transition-all flex items-center justify-center gap-2 group" title="{{ __('Copy Code') }}"> - + {{ __('Copy') }} diff --git a/resources/views/components/modal.blade.php b/resources/views/components/modal.blade.php index 384662a..d266df4 100644 --- a/resources/views/components/modal.blade.php +++ b/resources/views/components/modal.blade.php @@ -1,21 +1,20 @@ @props([ - 'name', - 'show' => false, - 'maxWidth' => '2xl' +'name', +'show' => false, +'maxWidth' => '2xl' ]) @php $maxWidth = [ - 'sm' => 'sm:max-w-sm', - 'md' => 'sm:max-w-md', - 'lg' => 'sm:max-w-lg', - 'xl' => 'sm:max-w-xl', - '2xl' => 'sm:max-w-2xl', +'sm' => 'sm:max-w-sm', +'md' => 'sm:max-w-md', +'lg' => 'sm:max-w-lg', +'xl' => 'sm:max-w-xl', +'2xl' => 'sm:max-w-2xl', ][$maxWidth]; @endphp -
-
+ })" x-on:open-modal.window="$event.detail == '{{ $name }}' ? show = true : null" + x-on:close-modal.window="$event.detail == '{{ $name }}' ? show = false : null" x-on:close.stop="show = false" + x-on:keydown.escape.window="show = false" x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()" + x-on:keydown.shift.tab.prevent="prevFocusable().focus()" x-show="show" + class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50" style="display: {{ $show ? 'block' : 'none' }};"> +
-
+ x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"> {{ $slot }}
-
+
\ No newline at end of file diff --git a/resources/views/components/searchable-select.blade.php b/resources/views/components/searchable-select.blade.php index e44ab51..484564a 100644 --- a/resources/views/components/searchable-select.blade.php +++ b/resources/views/components/searchable-select.blade.php @@ -1,52 +1,70 @@ @props([ - 'name' => null, - 'options' => [], - 'selected' => null, - 'placeholder' => null, - 'id' => null, - 'hasSearch' => true, +'name' => null, +'options' => [], +'selected' => null, +'placeholder' => null, +'id' => null, +'hasSearch' => true, ]) @php - $id = $id ?? $name ?? 'select-' . uniqid(); - $options = is_iterable($options) ? $options : []; - - // Skill Standard: Use " " for empty/all options to bypass Preline hiding while staying 'blank' - $isEmptySelected = (is_null($selected) || (string)$selected === '' || (string)$selected === ' '); - - $config = [ - "placeholder" => $placeholder ?: __('Select...'), - "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", - "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" => '', - "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" => '
' - ]; +$id = $id ?? $name ?? 'select-' . uniqid(); +$options = is_iterable($options) ? $options : []; + +// Skill Standard: Use " " for empty/all options to bypass Preline hiding while staying 'blank' +$isEmptySelected = (is_null($selected) || (string)$selected === '' || (string)$selected === ' '); + +$config = [ +"placeholder" => $placeholder ?: __('Select...'), +"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", +"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" => '', +"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" => '
' +]; @endphp
merge(['class' => 'relative w-full'])->only('class') }}> - except(['options', 'selected', 'placeholder', 'id', 'name', 'class', 'hasSearch']) }}> @if($placeholder) - + @endif - + {{ $slot }} @foreach($options as $v => $l) - @php - $val = is_object($l) ? ($l->id ?? $l->value) : $v; - $text = is_object($l) ? ($l->name ?? $l->label ?? $l->title) : $l; - @endphp - + @php + $val = is_object($l) ? ($l->id ?? $l->value) : $v; + $text = is_object($l) ? ($l->name ?? $l->label ?? $l->title) : $l; + @endphp + @endforeach
\ No newline at end of file