[REFACTOR] 支付方式多語系化與 UI 響應式優化

1. [REFACTOR] Order 模型:將支付類型標籤改為多語系轉譯,支援繁中、英、日。
2. [FEAT] 補全 lang/*.json 中的支付類型翻譯。
3. [STYLE] layouts/admin.blade.php:優化電腦版與手機版邊距佈局。
4. [STYLE] 銷售紀錄分頁:優化手機版 info grid 響應式顯示。
5. [FIX] 銷售紀錄:恢復手機版完整日期時間格式。
6. [DELETE] 移除 B710 (Timer Sync) 冗餘代碼與路由。
7. [DOCS] 更新 API 文件配置。
This commit is contained in:
sky121113 2026-05-11 11:01:48 +08:00
parent c774d46243
commit 284d56a544
13 changed files with 510 additions and 474 deletions

View File

@ -7,7 +7,6 @@ use Illuminate\Http\Request;
use App\Models\Machine\Machine;
use App\Models\System\User;
use App\Jobs\Machine\ProcessHeartbeat;
use App\Jobs\Machine\ProcessTimerStatus;
use App\Jobs\Machine\ProcessCoinInventory;
use App\Jobs\Machine\ProcessMachineError;
use App\Jobs\Machine\ProcessStateLog;
@ -197,18 +196,6 @@ class MachineController extends Controller
}
/**
* B710: Sync Timer status (Asynchronous)
*/
public function syncTimer(Request $request)
{
$machine = $request->get('machine');
$data = $request->except(['machine', 'key']); // 排除 Middleware 注入物件
ProcessTimerStatus::dispatch($machine->serial_no, $data);
return response()->json(['success' => true], 202);
}
/**
* B220: Sync Coin Inventory (Asynchronous)

View File

@ -1,51 +0,0 @@
<?php
namespace App\Jobs\Machine;
use App\Models\Machine\Machine;
use App\Models\Machine\TimerStatus;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class ProcessTimerStatus implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $serialNo;
protected $data;
/**
* Create a new job instance.
*/
public function __construct(string $serialNo, array $data)
{
$this->serialNo = $serialNo;
$this->data = $data;
}
/**
* Execute the job.
*/
public function handle(): void
{
try {
$machine = Machine::where('serial_no', $this->serialNo)->firstOrFail();
TimerStatus::updateOrCreate(
['machine_id' => $machine->id, 'slot_no' => $this->data['slot_no']],
[
'status' => $this->data['status'],
'remaining_seconds' => $this->data['remaining_seconds'],
'end_at' => isset($this->data['end_at']) ? \Carbon\Carbon::parse($this->data['end_at']) : null,
]
);
} catch (\Exception $e) {
Log::error("Failed to process timer status for machine {$this->serialNo}: " . $e->getMessage());
throw $e;
}
}
}

View File

@ -1,28 +0,0 @@
<?php
namespace App\Models\Machine;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TimerStatus extends Model
{
use HasFactory;
protected $fillable = [
'machine_id',
'slot_no',
'status',
'remaining_seconds',
'end_at',
];
protected $casts = [
'end_at' => 'datetime',
];
public function machine()
{
return $this->belongsTo(Machine::class);
}
}

View File

@ -74,11 +74,11 @@ class Order extends Model
23 => __('Offline + 3'),
24 => __('Offline + 4'),
25 => __('Offline + 9'),
30 => 'LINE Pay',
30 => __('LINE Pay'),
31 => __('JKO Pay'),
32 => __('Easy Wallet'),
33 => 'Pi Pay',
34 => 'PlusPay',
33 => __('Pi Pay'),
34 => __('PlusPay'),
40 => __('Member Verify Pickup'),
41 => __('Staff Card'),
50 => __('Offline + LINE Pay'),

View File

@ -409,22 +409,6 @@ return [
],
'notes' => '驗證成功後會建立刷卡日誌。連續錯誤同樣會觸發鎖定。'
],
[
'name' => 'B710: 機台時間同步 (Timer Sync)',
'slug' => 'b710-timer-sync',
'method' => 'POST',
'path' => '/api/v1/app/machine/timer/B710',
'description' => '校準機台 RTC 時間。',
'headers' => [
'Authorization' => 'Bearer <api_token>',
],
'response' => [
'success' => true,
'data' => [
'timestamp' => 1714444800
]
]
],
[
'name' => 'B027: 贈品碼/優惠券驗證 (Free Gift Verify)',
'slug' => 'b027-freebie-code',

View File

@ -200,6 +200,17 @@
"Cancel Code": "Cancel Code",
"Cancel Order": "Cancel Order",
"Cancel Pass Code": "Cancel Pass Code",
"Offline + 1": "Offline + 1",
"Offline + 2": "Offline + 2",
"Offline + 3": "Offline + 3",
"Offline + 4": "Offline + 4",
"Offline + 9": "Offline + 9",
"Offline + LINE Pay": "Offline + LINE Pay",
"Offline + JKO Pay": "Offline + JKO Pay",
"Offline + Easy Wallet": "Offline + Easy Wallet",
"Offline + Pi Pay": "Offline + Pi Pay",
"Offline + PlusPay": "Offline + PlusPay",
"Points/Voucher": "Points/Voucher",
"Cancel Pickup Code": "Cancel Pickup Code",
"Cancel Purchase": "Cancel Purchase",
"Cancelled": "Cancelled",
@ -625,6 +636,9 @@
"LINE Pay Direct": "LINE Pay Direct",
"LINE Pay Direct Settings Description": "LINE Pay Official Direct Connection Settings",
"LINE_MERCHANT_ID": "LINE_MERCHANT_ID",
"LINE Pay": "LINE Pay",
"Pi Pay": "Pi Pay",
"PlusPay": "PlusPay",
"LIVE": "LIVE",
"Last Communication": "Last Communication",
"Last Heartbeat": "Last Heartbeat",
@ -779,9 +793,24 @@
"Member": "Member",
"Member & External": "Member & External",
"Member List": "Member List",
"Member + 1": "Member + 1",
"Member + 2": "Member + 2",
"Member + 3": "Member + 3",
"Member + 4": "Member + 4",
"Member + 5": "Member + 5",
"Member + 6": "Member + 6",
"Member + 7": "Member + 7",
"Member + 8": "Member + 8",
"Member + 9": "Member + 9",
"Member + LINE Pay": "Member + LINE Pay",
"Member + JKO Pay": "Member + JKO Pay",
"Member + Easy Wallet": "Member + Easy Wallet",
"Member + Pi Pay": "Member + Pi Pay",
"Member + PlusPay": "Member + PlusPay",
"Member Management": "Member Management",
"Member Price": "Member Price",
"Member Status": "Member Status",
"Member Verify Pickup": "Member Verify Pickup",
"Member System": "Member System",
"Membership Tiers": "Membership Tiers",
"Menu Permissions": "Menu Permissions",
@ -1329,6 +1358,7 @@
"Spring": "Spring",
"Spring Channel Limit": "Spring Channel Limit",
"Spring Limit": "Spring Limit",
"Staff Card": "Staff Card",
"Staff Stock": "Staff Stock",
"Standby": "Standby",
"Standby Ad": "Standby Ad",

View File

@ -200,6 +200,17 @@
"Cancel Order": "注文取消",
"Cancel Pass Code": "通行コードをキャンセル",
"Cancel Pickup Code": "受取コードをキャンセル",
"Offline + 1": "オフライン + 1",
"Offline + 2": "オフライン + 2",
"Offline + 3": "オフライン + 3",
"Offline + 4": "オフライン + 4",
"Offline + 9": "オフライン + 9",
"Offline + LINE Pay": "オフライン + LINE Pay",
"Offline + JKO Pay": "オフライン + JKO Pay",
"Offline + Easy Wallet": "オフライン + Easy Wallet",
"Offline + Pi Pay": "オフライン + Pi Pay",
"Offline + PlusPay": "オフライン + PlusPay",
"Points/Voucher": "ポイント/クーポン",
"Cancel Purchase": "Cancel Purchase",
"Cancelled": "キャンセル済み",
"Cannot Delete Role": "Cannot Delete Role",
@ -228,6 +239,8 @@
"Category Name (en)": "Category Name (en)",
"Category Name (ja)": "Category Name (ja)",
"Category Name (zh_TW)": "Category Name (zh_TW)",
"E-Ticket (EasyCard/iPass)": "電子マネー (悠遊カード/iPass)",
"Easy Wallet": "悠遊付 (Easy Wallet)",
"Change": "Change",
"Change Note": "Change Note",
"Change Stock": "Change Stock",
@ -250,6 +263,7 @@
"Code Copied": "Code Copied",
"Coin/Banknote Machine": "硬貨/紙幣モジュール",
"Coin/Bill Acceptor": "コイン/紙幣機",
"Coin Acceptor": "硬貨識別機",
"Command Center": "Command Center",
"Command Confirmation": "Command Confirmation",
"Command Type": "Command Type",
@ -621,7 +635,10 @@
"LINE Pay Direct": "LINE Pay Direct",
"LINE Pay Direct Settings Description": "LINE Pay Direct Settings Description",
"LINE_MERCHANT_ID": "LINE_MERCHANT_ID",
"LIVE": "LIVE",
"LINE Pay": "LINE Pay",
"Pi Pay": "Pi Pay",
"PlusPay": "PlusPay",
"Inactive": "Inactive",
"Last Communication": "Last Communication",
"Last Heartbeat": "Last Heartbeat",
"Last Page": "Last Page",
@ -769,12 +786,27 @@
"Max Capacity": "最大容量",
"Max Capacity:": "Max Capacity:",
"Max Stock": "最大在庫",
"Member + 1": "会員 + 1",
"Member + 2": "会員 + 2",
"Member + 3": "会員 + 3",
"Member + 4": "会員 + 4",
"Member + 5": "会員 + 5",
"Member + 6": "会員 + 6",
"Member + 7": "会員 + 7",
"Member + 8": "会員 + 8",
"Member + 9": "会員 + 9",
"Member + LINE Pay": "会員 + LINE Pay",
"Member + JKO Pay": "会員 + 街口支付",
"Member + Easy Wallet": "会員 + 悠遊付",
"Member + Pi Pay": "会員 + Pi 拍錢包",
"Member + PlusPay": "会員 + 全盈+PAY",
"Member": "Member",
"Member & External": "Member & External",
"Member List": "Member List",
"Member Management": "Member Management",
"Member Price": "Member Price",
"Member Status": "Member Status",
"Member Verify Pickup": "会員認証受取",
"Member System": "Member System",
"Membership Tiers": "Membership Tiers",
"Menu Permissions": "Menu Permissions",
@ -1323,6 +1355,7 @@
"Spring": "スプリング",
"Spring Channel Limit": "Spring Channel Limit",
"Spring Limit": "Spring Limit",
"Staff Card": "スタッフカード",
"Staff Stock": "Staff Stock",
"Standby": "Standby",
"Standby Ad": "Standby Ad",

View File

@ -81,6 +81,7 @@
"Advertisement deleted successfully.": "廣告刪除成功。",
"Advertisement updated successfully": "廣告更新成功",
"Advertisement updated successfully.": "廣告更新成功。",
"Bill Acceptor": "紙鈔機",
"Affiliated Company": "所屬公司",
"Affiliated Unit": "公司名稱",
"Affiliation": "所屬單位",
@ -257,6 +258,7 @@
"Code Copied": "代碼已複製",
"Coin/Banknote Machine": "硬幣機/紙鈔機",
"Coin/Bill Acceptor": "硬幣機/紙鈔機",
"Coin Acceptor": "硬幣機",
"Command Center": "指令中心",
"Command Confirmation": "指令確認",
"Command Type": "指令類型",
@ -470,6 +472,8 @@
"Edit Staff Card": "編輯員工識別卡",
"Edit Sub Account Role": "編輯子帳號角色",
"Edit Warehouse": "編輯倉庫",
"E-Ticket (EasyCard/iPass)": "電子票證 (悠遊卡/一卡通)",
"Easy Wallet": "悠遊付",
"Electronic Invoice": "電子發票",
"Electronic Invoices": "電子發票",
"Elevator descending": "升降平台下降中",
@ -712,6 +716,7 @@
"Issued At": "開立時間",
"Item List": "商品清單",
"Items": "筆",
"JKO Pay": "街口支付",
"JKO_MERCHANT_ID": "街口支付 商店代號",
"Japanese": "日文",
"Inactive": "已停用",
@ -722,7 +727,8 @@
"LEVEL TYPE": "層級類型",
"LINE Pay Direct": "LINE Pay 官方直連",
"LINE Pay Direct Settings Description": "LINE Pay 官方直連設定",
"LINE_MERCHANT_ID": "LINE Pay 商店代號",
"LINE Pay": "LINE Pay",
"LINE_PAY_CHANNEL_ID": "LINE Pay 頻道 ID",
"LIVE": "實時",
"Last Communication": "最後通訊",
"Last Heartbeat": "最後心跳時間",
@ -882,7 +888,22 @@
"Member List": "會員列表",
"Member Management": "會員管理",
"Member Price": "會員價",
"Member + 1": "會員 + 1",
"Member + 2": "會員 + 2",
"Member + 3": "會員 + 3",
"Member + 4": "會員 + 4",
"Member + 5": "會員 + 5",
"Member + 6": "會員 + 6",
"Member + 7": "會員 + 7",
"Member + 8": "會員 + 8",
"Member + 9": "會員 + 9",
"Member + LINE Pay": "會員 + LINE Pay",
"Member + JKO Pay": "會員 + 街口支付",
"Member + Easy Wallet": "會員 + 悠遊付",
"Member + Pi Pay": "會員 + Pi 拍錢包",
"Member + PlusPay": "會員 + 全盈+PAY",
"Member Status": "會員狀態",
"Member Verify Pickup": "會員驗證取貨",
"Member System": "會員系統",
"Membership Tiers": "會員等級",
"Menu Permissions": "選單權限",
@ -1151,10 +1172,22 @@
"Please select a material": "請選擇素材",
"Please select a slot": "請選擇貨道",
"Please select warehouse and machine": "請選擇倉庫和機台",
"Pi Pay": "Pi 拍錢包",
"PlusPay": "全盈+PAY",
"Point Rules": "點數規則",
"Point Settings": "點數設定",
"Points": "點數功能",
"Points Rule": "點數規則",
"Offline + 1": "離線 + 1",
"Offline + 2": "離線 + 2",
"Offline + 3": "離線 + 3",
"Offline + 4": "離線 + 4",
"Offline + 9": "離線 + 9",
"Offline + LINE Pay": "離線 + LINE Pay",
"Offline + JKO Pay": "離線 + 街口支付",
"Offline + Easy Wallet": "離線 + 悠遊付",
"Offline + Pi Pay": "離線 + Pi 拍錢包",
"Offline + PlusPay": "離線 + 全盈+PAY",
"Points/Voucher": "點數/優惠券",
"Points Settings": "點數設定",
"Points toggle": "點數開關",
"Position": "投放位置",
@ -1206,6 +1239,7 @@
"Qty Change": "異動數量",
"Quality": "品質 (Quality)",
"Quantity": "數量",
"QR Code Payment": "掃碼支付",
"Questionnaire": "問卷",
"Quick Expiry Check": "效期快速檢查",
"Quick Maintenance": "快速維護",
@ -1444,6 +1478,7 @@
"Spring Channel Limit": "彈簧貨道上限",
"Spring Limit": "彈簧貨道上限",
"Staff": "員工",
"Staff Card": "員工卡",
"Staff Identification Management": "員工識別管理",
"Staff List": "員工列表",
"Staff Name": "員工姓名",

View File

@ -1,39 +1,35 @@
{{-- Search Bar --}}
<form action="{{ route('admin.sales.index') }}" method="GET"
class="flex flex-col lg:flex-row lg:items-center flex-wrap gap-3 mb-8"
@submit.prevent="fetchTabData('dispense')">
class="flex flex-col lg:flex-row lg:items-center flex-wrap gap-3 mb-8" @submit.prevent="fetchTabData('dispense')">
<input type="hidden" name="tab" value="dispense">
<div class="relative group flex-1 min-w-[280px]">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
<svg class="w-4 h-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors stroke-[2.5]"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round">
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</span>
<input type="text" name="search" value="{{ $tab === 'dispense' ? $filters['search'] : '' }}"
class="py-2.5 pl-12 pr-6 block w-full luxury-input"
placeholder="{{ __('Search Product / Slot...') }}">
class="py-2.5 pl-12 pr-6 block w-full luxury-input" placeholder="{{ __('Search Product / Slot...') }}">
</div>
<div class="w-full lg:w-48">
<x-searchable-select name="machine_id" :placeholder="__('All Machines')" :selected="$filters['machine_id']"
@change="$el.closest('form').dispatchEvent(new Event('submit'))">
@foreach($machines as $m)
<option value="{{ $m->id }}" {{ $filters['machine_id'] == $m->id ? 'selected' : '' }} data-title="{{ $m->name }}">
{{ $m->name }}
</option>
<option value="{{ $m->id }}" {{ $filters['machine_id']==$m->id ? 'selected' : '' }} data-title="{{ $m->name
}}">
{{ $m->name }}
</option>
@endforeach
</x-searchable-select>
</div>
{{-- 日期範圍 --}}
<div class="relative group w-full lg:w-72 lg:flex-none"
x-data="{ fp: null }"
x-init="fp = flatpickr($refs.dateRange, {
<div class="relative group w-full lg:w-72 lg:flex-none" x-data="{ fp: null }" x-init="fp = flatpickr($refs.dateRange, {
mode: 'range',
dateFormat: 'Y-m-d H:i',
enableTime: true,
@ -54,32 +50,33 @@
}
}
})">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10 text-slate-400 group-focus-within:text-cyan-500 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
<span
class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10 text-slate-400 group-focus-within:text-cyan-500 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</span>
<input type="hidden" name="start_date" x-ref="startDate" value="{{ $filters['start_date'] }}">
<input type="hidden" name="end_date" x-ref="endDate" value="{{ $filters['end_date'] }}">
<input type="text" x-ref="dateRange"
value="{{ $filters['start_date'] && $filters['end_date'] ? $filters['start_date'] . ' 至 ' . $filters['end_date'] : ($filters['start_date'] ?: '') }}"
placeholder="{{ __('Select Date Range') }}" class="luxury-input py-2.5 pl-12 pr-6 block w-full text-sm font-bold cursor-pointer">
placeholder="{{ __('Select Date Range') }}"
class="luxury-input py-2.5 pl-12 pr-6 block w-full text-sm font-bold cursor-pointer">
</div>
<div class="flex items-center gap-2 ml-auto lg:ml-0 shrink-0">
<button type="submit"
class="p-2.5 rounded-xl bg-cyan-500 text-white hover:bg-cyan-600 shadow-lg shadow-cyan-500/25 transition-all active:scale-95"
title="{{ __('Search') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
<button type="button"
@click="switchTab('dispense', '{{ route('admin.sales.index', ['tab' => 'dispense']) }}')"
<button type="button" @click="switchTab('dispense', '{{ route('admin.sales.index', ['tab' => 'dispense']) }}')"
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all active:scale-95 border border-slate-200 dark:border-slate-700"
title="{{ __('Reset') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
stroke-width="2.5">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
@ -92,88 +89,93 @@
<table class="w-full text-left border-separate border-spacing-0">
<thead>
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Machine') }}
</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Product / Slot') }}
</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Amount') }}
</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Dispense Status') }}
</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Dispense Time') }}
</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">
{{ __('Associated Order') }}
</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-800/80">
@forelse($dispenseLogs as $log)
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200 {{ $log->order_id ? 'cursor-pointer' : '' }}" @click="if({{ $log->order_id ?? 'null' }}) openDetail({{ $log->order_id }})">
<td class="px-6 py-6">
<div class="text-sm font-extrabold text-slate-800 dark:text-slate-100 tracking-tight">
{{ $log->machine->name ?? 'Unknown' }}
</div>
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em] mt-1">
{{ $log->machine->serial_no ?? '---' }}
</div>
</td>
<td class="px-6 py-6">
<div class="text-sm font-black text-cyan-600 dark:text-cyan-400 mb-0.5">
{{ $log->product->name ?? 'Unknown Product' }}
</div>
<div class="text-xs font-bold text-slate-400 uppercase tracking-widest">
{{ __('Slot') }}: {{ $log->slot_no }}
</div>
</td>
<td class="px-6 py-6">
<span class="text-sm font-black text-slate-700 dark:text-slate-200">
{{ $log->amount }}
</span>
</td>
<td class="px-6 py-6 whitespace-nowrap">
@php
$statusMap = [
'success' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
'failed' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
'pending' => ['label' => __('Dispense Pending'), 'color' => 'amber'],
'1' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
'0' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
];
$statusKey = (string)($log->dispense_status ?? 'pending');
$s = $statusMap[$statusKey] ?? ['label' => $statusKey, 'color' => 'slate'];
@endphp
<x-status-badge :color="$s['color']" :label="$s['label']" size="xs" />
</td>
<td class="px-6 py-6">
<span class="text-sm font-black text-slate-700 dark:text-slate-200">
{{ $log->machine_time }}
</span>
</td>
<td class="px-6 py-6 text-right">
@if($log->order)
<button
@click.stop="openDetail({{ $log->order_id }})"
class="text-sm font-black text-slate-600 dark:text-slate-400 bg-slate-50 dark:bg-slate-800 px-3 py-1.5 rounded-lg border border-slate-100 dark:border-slate-700 hover:text-cyan-500 transition-all"
>
{{ $log->order->order_no }}
</button>
@else
<span class="text-xs text-slate-300 italic">{{ __('No associated order found') }}</span>
@endif
</td>
</tr>
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200 {{ $log->order_id ? 'cursor-pointer' : '' }}"
@click="if({{ $log->order_id ?? 'null' }}) openDetail({{ $log->order_id }})">
<td class="px-6 py-6">
<div class="text-sm font-extrabold text-slate-800 dark:text-slate-100 tracking-tight">
{{ $log->machine->name ?? 'Unknown' }}
</div>
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em] mt-1">
{{ $log->machine->serial_no ?? '---' }}
</div>
</td>
<td class="px-6 py-6">
<div class="text-sm font-black text-cyan-600 dark:text-cyan-400 mb-0.5">
{{ $log->product->name ?? 'Unknown Product' }}
</div>
<div class="text-xs font-bold text-slate-400 uppercase tracking-widest">
{{ __('Slot') }}: {{ $log->slot_no }}
</div>
</td>
<td class="px-6 py-6">
<span class="text-sm font-black text-slate-700 dark:text-slate-200">
{{ $log->amount }}
</span>
</td>
<td class="px-6 py-6 whitespace-nowrap">
@php
$statusMap = [
'success' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
'failed' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
'pending' => ['label' => __('Dispense Pending'), 'color' => 'amber'],
'1' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
'0' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
];
$statusKey = (string)($log->dispense_status ?? 'pending');
$s = $statusMap[$statusKey] ?? ['label' => $statusKey, 'color' => 'slate'];
@endphp
<x-status-badge :color="$s['color']" :label="$s['label']" size="xs" />
</td>
<td class="px-6 py-6">
<span class="text-sm font-black text-slate-700 dark:text-slate-200">
{{ $log->machine_time }}
</span>
</td>
<td class="px-6 py-6 text-right">
@if($log->order)
<button @click.stop="openDetail({{ $log->order_id }})"
class="text-sm font-black text-slate-600 dark:text-slate-400 bg-slate-50 dark:bg-slate-800 px-3 py-1.5 rounded-lg border border-slate-100 dark:border-slate-700 hover:text-cyan-500 transition-all">
{{ $log->order->order_no }}
</button>
@else
<span class="text-xs text-slate-300 italic">{{ __('No associated order found') }}</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="6" class="py-20 text-center">
<x-empty-state mode="table" colspan="6" :message="__('No dispense records found')" />
</td>
</tr>
<tr>
<td colspan="6" class="py-20 text-center">
<x-empty-state mode="table" colspan="6" :message="__('No dispense records found')" />
</td>
</tr>
@endforelse
</tbody>
</table>
@ -182,89 +184,100 @@
{{-- Mobile Cards (xl:hidden) --}}
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
@forelse($dispenseLogs as $log)
<div class="luxury-card p-6 rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group">
@php
$statusMap = [
'success' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
'failed' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
'pending' => ['label' => __('Dispense Pending'), 'color' => 'amber'],
'1' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
'0' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
];
$statusKey = (string)($log->dispense_status ?? 'pending');
$s = $statusMap[$statusKey] ?? ['label' => $statusKey, 'color' => 'slate'];
@endphp
<div
class="luxury-card p-6 rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group">
@php
$statusMap = [
'success' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
'failed' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
'pending' => ['label' => __('Dispense Pending'), 'color' => 'amber'],
'1' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
'0' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
];
$statusKey = (string)($log->dispense_status ?? 'pending');
$s = $statusMap[$statusKey] ?? ['label' => $statusKey, 'color' => 'slate'];
@endphp
{{-- Card Header --}}
<div class="flex items-start justify-between gap-4 mb-6">
<div class="flex items-center gap-4 min-w-0">
<div class="w-14 h-14 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden shadow-sm shrink-0">
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707.293l-2.414-2.414A1 1 0 006.586 13H4"/>
</svg>
</div>
<div class="min-w-0">
<h3 class="text-base font-extrabold text-slate-800 dark:text-slate-100 truncate tracking-tight">
{{ $log->machine->name ?? 'Unknown' }}
</h3>
<p class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest truncate">
{{ $log->machine->serial_no ?? '---' }}
</p>
</div>
{{-- Card Header --}}
<div class="flex items-start justify-between gap-4 mb-6">
<div class="flex items-center gap-4 min-w-0">
<div
class="w-14 h-14 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden shadow-sm shrink-0">
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707.293l-2.414-2.414A1 1 0 006.586 13H4" />
</svg>
</div>
<x-status-badge :color="$s['color']" :label="$s['label']" size="sm" />
</div>
{{-- Info Grid --}}
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Product / Slot') }}</p>
<div class="flex flex-col gap-0.5 min-w-0">
<p class="text-sm font-black text-cyan-600 dark:text-cyan-400 truncate">
{{ $log->product->name ?? 'Unknown' }}
</p>
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">
{{ __('Slot') }}: {{ $log->slot_no }}
</p>
</div>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Amount') }}</p>
<p class="text-sm font-black text-slate-700 dark:text-slate-300">{{ $log->amount }}</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Dispense Time') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ $log->machine_time }}</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Associated Order') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 truncate">
{{ $log->order->order_no ?? '---' }}
<div class="min-w-0">
<h3 class="text-base font-extrabold text-slate-800 dark:text-slate-100 truncate tracking-tight">
{{ $log->machine->name ?? 'Unknown' }}
</h3>
<p
class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest truncate">
{{ $log->machine->serial_no ?? '---' }}
</p>
</div>
</div>
<x-status-badge :color="$s['color']" :label="$s['label']" size="sm" />
</div>
{{-- Action Buttons --}}
<div class="flex items-center gap-3">
@if($log->order_id)
<button @click="openDetail({{ $log->order_id }})" class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-600 dark:text-slate-300 font-black text-xs uppercase tracking-widest border border-slate-100 dark:border-slate-800 hover:text-cyan-500 hover:bg-cyan-500/5 transition-all duration-300">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
{{ __('View Order') }}
</button>
@else
<div class="flex-1 py-3 text-center text-[10px] font-bold text-slate-400 uppercase tracking-widest bg-slate-50/50 dark:bg-slate-800/30 rounded-xl border border-dashed border-slate-200 dark:border-slate-700">
{{ __('No related detail found') }}
</div>
@endif
{{-- Info Grid --}}
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Product / Slot') }}</p>
<div class="flex flex-col gap-0.5 min-w-0">
<p class="text-sm font-black text-cyan-600 dark:text-cyan-400 truncate">
{{ $log->product->name ?? 'Unknown' }}
</p>
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">
{{ __('Slot') }}: {{ $log->slot_no }}
</p>
</div>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Amount') }}</p>
<p class="text-sm font-black text-slate-700 dark:text-slate-300">{{ $log->amount }}</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Dispense Time') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ $log->machine_time }}</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Associated Order') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 truncate">
{{ $log->order->order_no ?? '---' }}
</p>
</div>
</div>
@empty
<div class="col-span-full py-10">
<x-empty-state :message="__('No dispense records found')" />
{{-- Action Buttons --}}
<div class="flex items-center gap-3">
@if($log->order_id)
<button @click="openDetail({{ $log->order_id }})"
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-600 dark:text-slate-300 font-black text-xs uppercase tracking-widest border border-slate-100 dark:border-slate-800 hover:text-cyan-500 hover:bg-cyan-500/5 transition-all duration-300">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
{{ __('View Order') }}
</button>
@else
<div
class="flex-1 py-3 text-center text-[10px] font-bold text-slate-400 uppercase tracking-widest bg-slate-50/50 dark:bg-slate-800/30 rounded-xl border border-dashed border-slate-200 dark:border-slate-700">
{{ __('No related detail found') }}
</div>
@endif
</div>
</div>
@empty
<div class="col-span-full py-10">
<x-empty-state :message="__('No dispense records found')" />
</div>
@endforelse
</div>

View File

@ -1,15 +1,13 @@
{{-- Search Bar --}}
<form action="{{ route('admin.sales.index') }}" method="GET"
class="flex flex-col lg:flex-row lg:items-center flex-wrap gap-3 mb-8"
@submit.prevent="fetchTabData('invoices')">
class="flex flex-col lg:flex-row lg:items-center flex-wrap gap-3 mb-8" @submit.prevent="fetchTabData('invoices')">
<input type="hidden" name="tab" value="invoices">
<div class="relative group flex-1 min-w-[280px]">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
<svg class="w-4 h-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors stroke-[2.5]"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round">
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
@ -23,17 +21,16 @@
<x-searchable-select name="machine_id" :placeholder="__('All Machines')" :selected="$filters['machine_id']"
@change="$el.closest('form').dispatchEvent(new Event('submit'))">
@foreach($machines as $m)
<option value="{{ $m->id }}" {{ $filters['machine_id'] == $m->id ? 'selected' : '' }} data-title="{{ $m->name }}">
{{ $m->name }}
</option>
<option value="{{ $m->id }}" {{ $filters['machine_id']==$m->id ? 'selected' : '' }} data-title="{{ $m->name
}}">
{{ $m->name }}
</option>
@endforeach
</x-searchable-select>
</div>
{{-- 日期範圍 --}}
<div class="relative group w-full lg:w-72 lg:flex-none"
x-data="{ fp: null }"
x-init="fp = flatpickr($refs.dateRange, {
<div class="relative group w-full lg:w-72 lg:flex-none" x-data="{ fp: null }" x-init="fp = flatpickr($refs.dateRange, {
mode: 'range',
dateFormat: 'Y-m-d H:i',
enableTime: true,
@ -54,32 +51,33 @@
}
}
})">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10 text-slate-400 group-focus-within:text-cyan-500 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
<span
class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10 text-slate-400 group-focus-within:text-cyan-500 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</span>
<input type="hidden" name="start_date" x-ref="startDate" value="{{ $filters['start_date'] }}">
<input type="hidden" name="end_date" x-ref="endDate" value="{{ $filters['end_date'] }}">
<input type="text" x-ref="dateRange"
value="{{ $filters['start_date'] && $filters['end_date'] ? $filters['start_date'] . ' 至 ' . $filters['end_date'] : ($filters['start_date'] ?: '') }}"
placeholder="{{ __('Select Date Range') }}" class="luxury-input py-2.5 pl-12 pr-6 block w-full text-sm font-bold cursor-pointer">
placeholder="{{ __('Select Date Range') }}"
class="luxury-input py-2.5 pl-12 pr-6 block w-full text-sm font-bold cursor-pointer">
</div>
<div class="flex items-center gap-2 ml-auto lg:ml-0 shrink-0">
<button type="submit"
class="p-2.5 rounded-xl bg-cyan-500 text-white hover:bg-cyan-600 shadow-lg shadow-cyan-500/25 transition-all active:scale-95"
title="{{ __('Search') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
<button type="button"
@click="switchTab('invoices', '{{ route('admin.sales.index', ['tab' => 'invoices']) }}')"
<button type="button" @click="switchTab('invoices', '{{ route('admin.sales.index', ['tab' => 'invoices']) }}')"
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all active:scale-95 border border-slate-200 dark:border-slate-700"
title="{{ __('Reset') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
stroke-width="2.5">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
@ -92,97 +90,111 @@
<table class="w-full text-left border-separate border-spacing-0">
<thead>
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Invoice Number') }}
</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Invoice Date') }}
</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Machine') }}
</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Amount') }}
</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Status') }}
</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Associated Order') }}
</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">
{{ __('Action') }}
</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-800/80">
@forelse($invoices as $invoice)
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200 cursor-pointer" @click="openDetail({{ $invoice->order_id }})">
<td class="px-6 py-6">
<div class="flex flex-col">
<span class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">
{{ $invoice->invoice_no }}
</span>
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1">
{{ ($invoice->machine_time ?? $invoice->created_at)->format('Y-m-d H:i:s') }}
</span>
</div>
</td>
<td class="px-6 py-6">
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 bg-slate-50 dark:bg-slate-800/50 px-2 py-1 rounded-md">
{{ $invoice->invoice_date }}
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200 cursor-pointer"
@click="openDetail({{ $invoice->order_id }})">
<td class="px-6 py-6">
<div class="flex flex-col">
<span
class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">
{{ $invoice->invoice_no }}
</span>
</td>
<td class="px-6 py-6">
<div class="text-sm font-extrabold text-slate-800 dark:text-slate-100 tracking-tight">
{{ $invoice->machine->name ?? ($invoice->order->machine->name ?? 'Unknown') }}
</div>
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em]">
{{ $invoice->machine->serial_no ?? ($invoice->order->machine->serial_no ?? '---') }}
</div>
</td>
<td class="px-6 py-6">
<span class="text-base font-black text-slate-800 dark:text-white tracking-tight">
${{ number_format($invoice->amount, 0) }}
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1">
{{ ($invoice->machine_time ?? $invoice->created_at)->format('Y-m-d H:i:s') }}
</span>
</td>
<td class="px-6 py-6 whitespace-nowrap">
@php
// 簡單判斷:如果有發票號碼且 rtn_code 為空或 0/1 則視為有效
$status = 'valid';
if (empty($invoice->invoice_no)) $status = 'failed';
if ($invoice->rtn_code === 'void') $status = 'void';
</div>
</td>
<td class="px-6 py-6">
<span
class="text-sm font-bold text-slate-600 dark:text-slate-300 bg-slate-50 dark:bg-slate-800/50 px-2 py-1 rounded-md">
{{ $invoice->invoice_date }}
</span>
</td>
<td class="px-6 py-6">
<div class="text-sm font-extrabold text-slate-800 dark:text-slate-100 tracking-tight">
{{ $invoice->machine->name ?? ($invoice->order->machine->name ?? 'Unknown') }}
</div>
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em]">
{{ $invoice->machine->serial_no ?? ($invoice->order->machine->serial_no ?? '---') }}
</div>
</td>
<td class="px-6 py-6">
<span class="text-base font-black text-slate-800 dark:text-white tracking-tight">
${{ number_format($invoice->amount, 0) }}
</span>
</td>
<td class="px-6 py-6 whitespace-nowrap">
@php
// 簡單判斷:如果有發票號碼且 rtn_code 為空或 0/1 則視為有效
$status = 'valid';
if (empty($invoice->invoice_no)) $status = 'failed';
if ($invoice->rtn_code === 'void') $status = 'void';
$statusMap = [
'valid' => ['label' => __('Valid'), 'color' => 'emerald'],
'void' => ['label' => __('Void'), 'color' => 'rose'],
'failed' => ['label' => __('Failed'), 'color' => 'rose'],
'refunded' => ['label' => __('Refunded'), 'color' => 'slate'],
];
$s = $statusMap[$status] ?? ['label' => $status, 'color' => 'slate'];
@endphp
<x-status-badge :color="$s['color']" :label="$s['label']" size="xs" />
</td>
<td class="px-6 py-6">
<span class="text-sm font-black text-slate-600 dark:text-slate-400 bg-slate-50 dark:bg-slate-800 px-3 py-1 rounded-lg border border-slate-100 dark:border-slate-700">
{{ $invoice->order->order_no ?? '---' }}
</span>
</td>
<td class="px-6 py-6 text-right">
<button class="p-2.5 rounded-xl text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
</td>
</tr>
$statusMap = [
'valid' => ['label' => __('Valid'), 'color' => 'emerald'],
'void' => ['label' => __('Void'), 'color' => 'rose'],
'failed' => ['label' => __('Failed'), 'color' => 'rose'],
'refunded' => ['label' => __('Refunded'), 'color' => 'slate'],
];
$s = $statusMap[$status] ?? ['label' => $status, 'color' => 'slate'];
@endphp
<x-status-badge :color="$s['color']" :label="$s['label']" size="xs" />
</td>
<td class="px-6 py-6">
<span
class="text-sm font-black text-slate-600 dark:text-slate-400 bg-slate-50 dark:bg-slate-800 px-3 py-1 rounded-lg border border-slate-100 dark:border-slate-700">
{{ $invoice->order->order_no ?? '---' }}
</span>
</td>
<td class="px-6 py-6 text-right">
<button
class="p-2.5 rounded-xl text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="py-20 text-center">
<x-empty-state mode="table" colspan="6" :message="__('No invoices found')" />
</td>
</tr>
<tr>
<td colspan="6" class="py-20 text-center">
<x-empty-state mode="table" colspan="6" :message="__('No invoices found')" />
</td>
</tr>
@endforelse
</tbody>
</table>
@ -191,89 +203,102 @@
{{-- Mobile Cards (xl:hidden) --}}
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
@forelse($invoices as $invoice)
<div class="luxury-card p-6 rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group">
@php
$status = 'valid';
if (empty($invoice->invoice_no)) $status = 'failed';
if ($invoice->rtn_code === 'void') $status = 'void';
$statusMap = [
'valid' => ['label' => __('Valid'), 'color' => 'emerald'],
'void' => ['label' => __('Void'), 'color' => 'rose'],
'failed' => ['label' => __('Failed'), 'color' => 'rose'],
'refunded' => ['label' => __('Refunded'), 'color' => 'slate'],
];
$s = $statusMap[$status] ?? ['label' => $status, 'color' => 'slate'];
@endphp
<div
class="luxury-card p-6 rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group">
@php
$status = 'valid';
if (empty($invoice->invoice_no)) $status = 'failed';
if ($invoice->rtn_code === 'void') $status = 'void';
$statusMap = [
'valid' => ['label' => __('Valid'), 'color' => 'emerald'],
'void' => ['label' => __('Void'), 'color' => 'rose'],
'failed' => ['label' => __('Failed'), 'color' => 'rose'],
'refunded' => ['label' => __('Refunded'), 'color' => 'slate'],
];
$s = $statusMap[$status] ?? ['label' => $status, 'color' => 'slate'];
@endphp
{{-- Card Header --}}
<div class="flex items-start justify-between gap-4 mb-6">
<div class="flex items-center gap-4 min-w-0">
<div class="w-14 h-14 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden shadow-sm shrink-0">
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
</div>
<div class="min-w-0">
<h3 class="text-base font-extrabold text-slate-800 dark:text-slate-100 truncate hover:text-cyan-600 dark:hover:text-cyan-400 transition-colors tracking-tight">
{{ $invoice->invoice_no }}
</h3>
<div class="flex items-center gap-2 mt-1">
<span class="px-1.5 py-0.5 rounded bg-cyan-50 dark:bg-cyan-500/10 text-[9px] font-black text-cyan-600 dark:text-cyan-400 border border-cyan-100 dark:border-cyan-500/20">
{{ $invoice->invoice_date }}
</span>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest truncate">
{{ ($invoice->machine_time ?? $invoice->created_at)->format('Y-m-d H:i:s') }}
</p>
</div>
</div>
</div>
<x-status-badge :color="$s['color']" :label="$s['label']" size="sm" />
</div>
{{-- Info Grid --}}
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Machine') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 truncate">
{{ $invoice->machine->name ?? ($invoice->order->machine->name ?? 'Unknown') }}
</p>
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-0.5">
{{ $invoice->machine->serial_no ?? ($invoice->order->machine->serial_no ?? '---') }}
</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Amount') }}</p>
<p class="text-base font-black text-cyan-600 dark:text-cyan-400">${{ number_format($invoice->amount, 0) }}</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Associated Order') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 truncate">
{{ $invoice->order->order_no ?? '---' }}
</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Machine Time') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">
{{ ($invoice->machine_time ?? $invoice->created_at)->format('Y-m-d H:i:s') }}
</p>
</div>
</div>
{{-- Action Buttons --}}
<div class="flex items-center gap-3">
<button @click="openDetail({{ $invoice->order_id }})" class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-600 dark:text-slate-300 font-black text-xs uppercase tracking-widest border border-slate-100 dark:border-slate-800 hover:text-cyan-500 hover:bg-cyan-500/5 transition-all duration-300">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
{{-- Card Header --}}
<div class="flex items-start justify-between gap-4 mb-6">
<div class="flex items-center gap-4 min-w-0">
<div
class="w-14 h-14 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden shadow-sm shrink-0">
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
{{ __('View Details') }}
</button>
</div>
<div class="min-w-0">
<h3
class="text-base font-extrabold text-slate-800 dark:text-slate-100 truncate hover:text-cyan-600 dark:hover:text-cyan-400 transition-colors tracking-tight">
{{ $invoice->invoice_no }}
</h3>
<div class="flex items-center gap-2 mt-1">
<span
class="px-1.5 py-0.5 rounded bg-cyan-50 dark:bg-cyan-500/10 text-[9px] font-black text-cyan-600 dark:text-cyan-400 border border-cyan-100 dark:border-cyan-500/20">
{{ $invoice->invoice_date }}
</span>
<p
class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest truncate">
{{ ($invoice->machine_time ?? $invoice->created_at)->format('Y-m-d H:i:s') }}
</p>
</div>
</div>
</div>
<x-status-badge :color="$s['color']" :label="$s['label']" size="sm" />
</div>
{{-- Info Grid --}}
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Machine') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 truncate">
{{ $invoice->machine->name ?? ($invoice->order->machine->name ?? 'Unknown') }}
</p>
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-0.5">
{{ $invoice->machine->serial_no ?? ($invoice->order->machine->serial_no ?? '---') }}
</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Amount') }}</p>
<p class="text-base font-black text-cyan-600 dark:text-cyan-400">${{ number_format($invoice->amount, 0)
}}</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Associated Order') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 truncate">
{{ $invoice->order->order_no ?? '---' }}
</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Machine Time') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">
{{ ($invoice->machine_time ?? $invoice->created_at)->format('Y-m-d H:i:s') }}
</p>
</div>
</div>
@empty
<div class="col-span-full py-10">
<x-empty-state :message="__('No invoices found')" />
{{-- Action Buttons --}}
<div class="flex items-center gap-3">
<button @click="openDetail({{ $invoice->order_id }})"
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-600 dark:text-slate-300 font-black text-xs uppercase tracking-widest border border-slate-100 dark:border-slate-800 hover:text-cyan-500 hover:bg-cyan-500/5 transition-all duration-300">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
{{ __('View Details') }}
</button>
</div>
</div>
@empty
<div class="col-span-full py-10">
<x-empty-state :message="__('No invoices found')" />
</div>
@endforelse
</div>

View File

@ -279,7 +279,7 @@
</div>
{{-- Info Grid --}}
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Order Items') }}</p>
@ -308,7 +308,7 @@
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Order Time') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">
{{ $order->created_at->format('m-d H:i') }}
{{ $order->created_at->format('Y-m-d H:i:s') }}
<span class="text-[10px] text-slate-400 ml-1">{{ $order->created_at->diffForHumans() }}</span>
</p>
</div>

View File

@ -110,35 +110,42 @@
window.location.reload();
}
}">
<div class="inline-flex items-center bg-white dark:bg-gray-800 rounded-full border border-slate-200/50 dark:border-slate-700/50 p-0.5 shadow-sm">
<div
class="inline-flex items-center bg-white dark:bg-gray-800 rounded-full border border-slate-200/50 dark:border-slate-700/50 p-0.5 shadow-sm">
<!-- Manual Refresh Button with Progress -->
<button type="button" @click="manualRefresh()"
:title="interval === 'Off' ? '{{ __('Refresh Now') }}' : '{{ __('Refreshing in :seconds s') }}'.replace(':seconds', timeLeft)"
class="relative flex items-center justify-center size-9 rounded-full text-gray-500 hover:text-cyan-500 hover:bg-gray-50 dark:hover:bg-slate-700 transition-all focus:outline-none">
<!-- Progress Ring -->
<svg class="absolute inset-0 size-full -rotate-90" viewBox="0 0 24 24">
<circle class="text-gray-100 dark:text-gray-700/50" stroke-width="2" stroke="currentColor" fill="transparent" r="9" cx="12" cy="12"/>
<circle class="text-gray-100 dark:text-gray-700/50" stroke-width="2" stroke="currentColor"
fill="transparent" r="9" cx="12" cy="12" />
<circle class="text-cyan-500 transition-all duration-1000 ease-linear" stroke-width="2"
:stroke-dasharray="circumference"
:stroke-dashoffset="circumference - (interval === 'Off' ? 0 : ((parseInt(interval) - timeLeft) / parseInt(interval)) * circumference)"
stroke-linecap="round" stroke="currentColor" fill="transparent" r="9" cx="12" cy="12"
x-show="interval !== 'Off'"/>
x-show="interval !== 'Off'" />
</svg>
<!-- Refresh Icon -->
<svg class="size-4 relative z-10" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
<path d="M21 3v5h-5"/>
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
<path d="M3 21v-5h5"/>
<svg class="size-4 relative z-10" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
<path d="M3 21v-5h5" />
</svg>
</button>
<!-- Interval Selector -->
<button type="button" @click="refreshOpen = !refreshOpen" @click.away="refreshOpen = false"
class="flex items-center gap-x-1.5 h-8 px-2 rounded-full text-[11px] font-bold text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-slate-700 transition-all border-l border-slate-100 dark:border-slate-700/50 ml-0.5 focus:outline-none md:min-w-[3.5rem] justify-center">
<span class="hidden md:inline whitespace-nowrap" x-text="interval === 'Off' ? '{{ __('Off') }}' : interval"></span>
<svg class="size-3 text-gray-400 shrink-0" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"/>
<span class="hidden md:inline whitespace-nowrap"
x-text="interval === 'Off' ? '{{ __('Off') }}' : interval"></span>
<svg class="size-3 text-gray-400 shrink-0" xmlns="http://www.w3.org/2000/svg" width="24"
height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6" />
</svg>
</button>
</div>
@ -160,7 +167,10 @@
class="flex items-center justify-between w-full py-2 px-3 rounded-xl text-sm text-gray-800 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 transition-colors"
:class="interval === opt ? 'bg-gray-50 dark:bg-gray-900/50 text-cyan-600 dark:text-cyan-400' : ''">
<span class="font-medium" x-text="opt === 'Off' ? '{{ __('Off') }}' : opt"></span>
<svg x-show="interval === opt" class="size-4 text-cyan-500" 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">
<svg x-show="interval === opt" class="size-4 text-cyan-500"
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" />
</svg>
</button>
@ -348,8 +358,7 @@
:class="[
sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0',
sidebarCollapsed ? 'w-[60px]' : 'w-56'
]"
x-init="$nextTick(() => {
]" x-init="$nextTick(() => {
const activeItem = $el.querySelector('.active, .bg-slate-100, .dark\\:bg-white\\/5');
if (activeItem) {
activeItem.scrollIntoView({ block: 'center', behavior: 'instant' });

View File

@ -60,7 +60,6 @@ Route::prefix('v1')->middleware(['throttle:api'])->group(function () {
// 機台狀態與相關同步 (B017, B710, B220)
Route::get('machine/reload_msg/B017', [App\Http\Controllers\Api\V1\App\MachineController::class, 'getSlots']);
Route::post('machine/timer/B710', [App\Http\Controllers\Api\V1\App\MachineController::class, 'syncTimer']);
Route::post('machine/coins/B220', [App\Http\Controllers\Api\V1\App\MachineController::class, 'syncCoinInventory']);
Route::post('machine/member/verify/B650', [App\Http\Controllers\Api\V1\App\MachineController::class, 'verifyMember']);