[FEAT] 機台日誌層級篩選與銷售紀錄付款/出貨狀態篩選

1. 機台日誌新增「層級」篩選下拉(所有層級/Info/Warning/Error),採 x-searchable-select 元件,選擇即時重撈,清除篩選一併重置
2. 銷售紀錄訂單分頁新增「付款狀態」篩選(對應 status 欄)
3. 銷售紀錄訂單分頁新增「出貨狀態」篩選(delivery_status),比照列表顯示邏輯限定 completed/paid 訂單,避免未出貨單因預設值誤篩
4. 補齊三語系 key:All Payment Statuses、All Dispense Statuses、Paid,並對齊排序

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-24 17:05:45 +08:00
parent 2736c382a2
commit f571f99084
6 changed files with 62 additions and 5 deletions

View File

@ -35,6 +35,7 @@ class SalesController extends Controller
$machineId = $request->input('machine_id');
$paymentType = $request->input('payment_type');
$status = $request->input('status');
$deliveryStatus = $request->input('delivery_status');
$startDate = $request->input('start_date');
$endDate = $request->input('end_date');
@ -55,6 +56,7 @@ class SalesController extends Controller
'machine_id' => $machineId,
'payment_type' => $paymentType,
'status' => $status,
'delivery_status' => $deliveryStatus,
'start_date' => $request->input('start_date'), // 保留原始輸入以顯示於 UI (如果是隱含的則 UI 顯示空)
'end_date' => $request->input('end_date'),
],
@ -126,6 +128,11 @@ class SalesController extends Controller
if ($tab === 'orders') {
if ($paymentType) $ordersQuery->where('payment_type', $paymentType);
if ($status) $ordersQuery->where('status', $status);
// 出貨狀態:唯有付款成功(completed/paid)的單才有出貨結果,需與列表顯示邏輯 (hasDeliveryOutcome) 一致
if ($deliveryStatus !== null && $deliveryStatus !== '') {
$ordersQuery->whereIn('status', [Order::STATUS_COMPLETED, 'paid'])
->where('delivery_status', $deliveryStatus);
}
}
// 發票專用過濾器狀態pending/issued/failed/void

View File

@ -110,9 +110,11 @@
"All Categories": "All Categories",
"All Command Types": "All Command Types",
"All Companies": "All Companies",
"All Dispense Statuses": "All Dispense Statuses",
"All Levels": "All Levels",
"All Machines": "All Machines",
"All Normal": "All Normal",
"All Payment Statuses": "All Payment Statuses",
"All Permissions": "All Permissions",
"All Products (ALL)": "All Products (ALL)",
"All Slots": "All Slots",
@ -1511,6 +1513,7 @@
"Page 69": "Page 69",
"Page 7": "Page 7",
"Page Lock Status": "Page Lock Status",
"Paid": "Paid",
"Parameters": "Parameters",
"Partial Dispense Success": "Partial Dispense Success",
"Pass Code": "Pass Code",

View File

@ -110,9 +110,11 @@
"All Categories": "すべてのカテゴリ",
"All Command Types": "すべてのコマンドタイプ",
"All Companies": "すべての会社",
"All Dispense Statuses": "すべての出庫ステータス",
"All Levels": "すべてのレベル",
"All Machines": "すべての機器",
"All Normal": "すべて正常",
"All Payment Statuses": "すべての決済ステータス",
"All Permissions": "すべての権限",
"All Products (ALL)": "全商品 (ALL)",
"All Slots": "すべてのスロット",
@ -1511,6 +1513,7 @@
"Page 69": "ページ 69 (購入キャンセル)",
"Page 7": "ページ 7 (ロックページ)",
"Page Lock Status": "ページロックステータス",
"Paid": "支払い済み",
"Parameters": "パラメータ設定",
"Partial Dispense Success": "一部出庫成功",
"Pass Code": "パスコード",

View File

@ -110,9 +110,11 @@
"All Categories": "所有分類",
"All Command Types": "所有指令類型",
"All Companies": "所有公司",
"All Dispense Statuses": "所有出貨狀態",
"All Levels": "所有層級",
"All Machines": "所有機台",
"All Normal": "運作正常",
"All Payment Statuses": "所有付款狀態",
"All Permissions": "全部權限",
"All Products (ALL)": "全部商品 (ALL)",
"All Slots": "所有貨道",
@ -1511,6 +1513,7 @@
"Page 69": "取消購買",
"Page 7": "鎖定頁",
"Page Lock Status": "頁面鎖定狀態",
"Paid": "已付款",
"Parameters": "參數設定",
"Partial Dispense Success": "部分出貨成功",
"Pass Code": "通行碼",

View File

@ -49,6 +49,7 @@
inventoryLoading: false,
startDate: '',
endDate: '',
selectedLevel: '',
tab: 'list',
viewMode: 'fleet',
selectedMachine: null,
@ -321,6 +322,7 @@
let url = '/admin/machines/' + this.currentMachineId + '/logs-ajax?type=' + this.activeTab + '&page=' + page;
if (this.startDate) url += '&start_date=' + this.startDate;
if (this.endDate) url += '&end_date=' + this.endDate;
if (this.selectedLevel) url += '&level=' + this.selectedLevel;
const res = await fetch(url);
const data = await res.json();
@ -993,7 +995,7 @@
<!-- Responsive Search within Panel -->
<div class="mt-2 flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-6">
<div class="grid grid-cols-1 sm:flex sm:items-center gap-3 sm:gap-4 flex-1">
<div class="grid grid-cols-1 sm:flex sm:flex-wrap sm:items-center gap-3 sm:gap-4 flex-1 min-w-0">
<div class="flex flex-col sm:flex-row sm:items-center gap-2 group">
<label
class="text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest whitespace-nowrap">{{
@ -1042,10 +1044,22 @@
class="luxury-input py-2.5 pl-12 pr-4 w-full sm:w-52 text-sm font-bold tracking-tight cursor-pointer">
</div>
</div>
<div class="flex flex-col sm:flex-row sm:items-center gap-2 group w-full sm:w-auto">
<label
class="text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest whitespace-nowrap">{{
__('Level') }}</label>
<x-searchable-select id="log-level-select" name="level" class="w-full sm:w-44"
:placeholder="__('All Levels')" :has-search="false"
@change="selectedLevel = $event.target.value.trim(); fetchLogs(1)">
<option value="info" data-title="{{ __('Info') }}">{{ __('Info') }}</option>
<option value="warning" data-title="{{ __('Warning') }}">{{ __('Warning') }}</option>
<option value="error" data-title="{{ __('Error') }}">{{ __('Error') }}</option>
</x-searchable-select>
</div>
<div class="flex flex-wrap items-center justify-start sm:justify-end gap-4 sm:gap-6">
</div>
<div class="flex flex-wrap items-center justify-start sm:justify-end gap-4 sm:gap-6 shrink-0">
<button @click="confirmResolve()"
class="text-[12px] font-bold text-rose-500 dark:text-rose-400 uppercase tracking-widest hover:text-rose-600 transition-colors flex items-center gap-1.5 px-3 py-1.5 rounded-xl hover:bg-rose-500/5 transition-all">
class="text-[12px] font-bold text-rose-500 dark:text-rose-400 uppercase tracking-widest hover:text-rose-600 transition-colors flex items-center gap-1.5 px-3 py-1.5 rounded-xl hover:bg-rose-500/5 transition-all whitespace-nowrap">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round"
@ -1053,8 +1067,10 @@
</svg>
{{ __('Clear Abnormal Status') }}
</button>
<button @click="startDate = ''; endDate = ''; fetchLogs(1)"
class="text-[12px] font-bold text-cyan-600 dark:text-cyan-400 uppercase tracking-widest hover:text-cyan-500 transition-colors flex items-center gap-1.5">
<button @click="startDate = ''; endDate = ''; selectedLevel = '';
(() => { const s = document.getElementById('log-level-select'); const i = window.HSSelect?.getInstance(s); if (i) i.setValue(' '); })();
fetchLogs(1)"
class="text-[12px] font-bold text-cyan-600 dark:text-cyan-400 uppercase tracking-widest hover:text-cyan-500 transition-colors flex items-center gap-1.5 whitespace-nowrap">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round"

View File

@ -29,6 +29,31 @@
</x-searchable-select>
</div>
{{-- 付款狀態 --}}
<div class="w-full lg:w-44">
<x-searchable-select name="status" :placeholder="__('All Payment Statuses')" :selected="$filters['status']"
:has-search="false" @change="$el.closest('form').dispatchEvent(new Event('submit'))">
<option value="pending" {{ $filters['status']==='pending' ? 'selected' : '' }} data-title="{{ __('Pending') }}">{{ __('Pending') }}</option>
<option value="paid" {{ $filters['status']==='paid' ? 'selected' : '' }} data-title="{{ __('Paid') }}">{{ __('Paid') }}</option>
<option value="completed" {{ $filters['status']==='completed' ? 'selected' : '' }} data-title="{{ __('Completed') }}">{{ __('Completed') }}</option>
<option value="failed" {{ $filters['status']==='failed' ? 'selected' : '' }} data-title="{{ __('Failed') }}">{{ __('Failed') }}</option>
<option value="abandoned" {{ $filters['status']==='abandoned' ? 'selected' : '' }} data-title="{{ __('Unpaid') }}">{{ __('Unpaid') }}</option>
<option value="cancelled" {{ $filters['status']==='cancelled' ? 'selected' : '' }} data-title="{{ __('Cancelled') }}">{{ __('Cancelled') }}</option>
<option value="refunded" {{ $filters['status']==='refunded' ? 'selected' : '' }} data-title="{{ __('Refunded') }}">{{ __('Refunded') }}</option>
</x-searchable-select>
</div>
{{-- 出貨狀態 --}}
<div class="w-full lg:w-44">
<x-searchable-select name="delivery_status" :placeholder="__('All Dispense Statuses')"
:selected="$filters['delivery_status']" :has-search="false"
@change="$el.closest('form').dispatchEvent(new Event('submit'))">
<option value="1" {{ (string)$filters['delivery_status']==='1' ? 'selected' : '' }} data-title="{{ __('Dispense Success') }}">{{ __('Dispense Success') }}</option>
<option value="2" {{ (string)$filters['delivery_status']==='2' ? 'selected' : '' }} data-title="{{ __('Partial Dispense Success') }}">{{ __('Partial Dispense Success') }}</option>
<option value="0" {{ (string)$filters['delivery_status']==='0' ? 'selected' : '' }} data-title="{{ __('Dispense Failed') }}">{{ __('Dispense Failed') }}</option>
</x-searchable-select>
</div>
{{-- 開始時間 --}}
<div class="relative group w-full lg:w-56 lg:flex-none"
x-data="{ fp: null }"