[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:
parent
c774d46243
commit
284d56a544
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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'),
|
||||
|
||||
@ -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',
|
||||
|
||||
30
lang/en.json
30
lang/en.json
@ -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",
|
||||
|
||||
35
lang/ja.json
35
lang/ja.json
@ -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",
|
||||
|
||||
@ -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": "員工姓名",
|
||||
|
||||
@ -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']"
|
||||
<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"
|
||||
<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,93 +184,104 @@
|
||||
{{-- 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>
|
||||
|
||||
{{-- Pagination --}}
|
||||
<div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
|
||||
{{ $dispenseLogs->links('vendor.pagination.luxury', ['page_param' => 'dispense_page']) }}
|
||||
</div>
|
||||
</div>
|
||||
@ -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>
|
||||
@ -20,20 +18,19 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full lg:w-48">
|
||||
<x-searchable-select name="machine_id" :placeholder="__('All Machines')" :selected="$filters['machine_id']"
|
||||
<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"
|
||||
<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';
|
||||
|
||||
$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>
|
||||
</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>
|
||||
@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,93 +203,106 @@
|
||||
{{-- 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>
|
||||
|
||||
{{-- Pagination --}}
|
||||
<div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
|
||||
{{ $invoices->links('vendor.pagination.luxury', ['page_param' => 'invoices_page']) }}
|
||||
</div>
|
||||
</div>
|
||||
@ -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>
|
||||
|
||||
@ -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()"
|
||||
<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-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)"
|
||||
<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' });
|
||||
|
||||
@ -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']);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user