[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\Machine\Machine;
|
||||||
use App\Models\System\User;
|
use App\Models\System\User;
|
||||||
use App\Jobs\Machine\ProcessHeartbeat;
|
use App\Jobs\Machine\ProcessHeartbeat;
|
||||||
use App\Jobs\Machine\ProcessTimerStatus;
|
|
||||||
use App\Jobs\Machine\ProcessCoinInventory;
|
use App\Jobs\Machine\ProcessCoinInventory;
|
||||||
use App\Jobs\Machine\ProcessMachineError;
|
use App\Jobs\Machine\ProcessMachineError;
|
||||||
use App\Jobs\Machine\ProcessStateLog;
|
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)
|
* 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'),
|
23 => __('Offline + 3'),
|
||||||
24 => __('Offline + 4'),
|
24 => __('Offline + 4'),
|
||||||
25 => __('Offline + 9'),
|
25 => __('Offline + 9'),
|
||||||
30 => 'LINE Pay',
|
30 => __('LINE Pay'),
|
||||||
31 => __('JKO Pay'),
|
31 => __('JKO Pay'),
|
||||||
32 => __('Easy Wallet'),
|
32 => __('Easy Wallet'),
|
||||||
33 => 'Pi Pay',
|
33 => __('Pi Pay'),
|
||||||
34 => 'PlusPay',
|
34 => __('PlusPay'),
|
||||||
40 => __('Member Verify Pickup'),
|
40 => __('Member Verify Pickup'),
|
||||||
41 => __('Staff Card'),
|
41 => __('Staff Card'),
|
||||||
50 => __('Offline + LINE Pay'),
|
50 => __('Offline + LINE Pay'),
|
||||||
|
|||||||
@ -409,22 +409,6 @@ return [
|
|||||||
],
|
],
|
||||||
'notes' => '驗證成功後會建立刷卡日誌。連續錯誤同樣會觸發鎖定。'
|
'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)',
|
'name' => 'B027: 贈品碼/優惠券驗證 (Free Gift Verify)',
|
||||||
'slug' => 'b027-freebie-code',
|
'slug' => 'b027-freebie-code',
|
||||||
|
|||||||
30
lang/en.json
30
lang/en.json
@ -200,6 +200,17 @@
|
|||||||
"Cancel Code": "Cancel Code",
|
"Cancel Code": "Cancel Code",
|
||||||
"Cancel Order": "Cancel Order",
|
"Cancel Order": "Cancel Order",
|
||||||
"Cancel Pass Code": "Cancel Pass Code",
|
"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 Pickup Code": "Cancel Pickup Code",
|
||||||
"Cancel Purchase": "Cancel Purchase",
|
"Cancel Purchase": "Cancel Purchase",
|
||||||
"Cancelled": "Cancelled",
|
"Cancelled": "Cancelled",
|
||||||
@ -625,6 +636,9 @@
|
|||||||
"LINE Pay Direct": "LINE Pay Direct",
|
"LINE Pay Direct": "LINE Pay Direct",
|
||||||
"LINE Pay Direct Settings Description": "LINE Pay Official Direct Connection Settings",
|
"LINE Pay Direct Settings Description": "LINE Pay Official Direct Connection Settings",
|
||||||
"LINE_MERCHANT_ID": "LINE_MERCHANT_ID",
|
"LINE_MERCHANT_ID": "LINE_MERCHANT_ID",
|
||||||
|
"LINE Pay": "LINE Pay",
|
||||||
|
"Pi Pay": "Pi Pay",
|
||||||
|
"PlusPay": "PlusPay",
|
||||||
"LIVE": "LIVE",
|
"LIVE": "LIVE",
|
||||||
"Last Communication": "Last Communication",
|
"Last Communication": "Last Communication",
|
||||||
"Last Heartbeat": "Last Heartbeat",
|
"Last Heartbeat": "Last Heartbeat",
|
||||||
@ -779,9 +793,24 @@
|
|||||||
"Member": "Member",
|
"Member": "Member",
|
||||||
"Member & External": "Member & External",
|
"Member & External": "Member & External",
|
||||||
"Member List": "Member List",
|
"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 Management": "Member Management",
|
||||||
"Member Price": "Member Price",
|
"Member Price": "Member Price",
|
||||||
"Member Status": "Member Status",
|
"Member Status": "Member Status",
|
||||||
|
"Member Verify Pickup": "Member Verify Pickup",
|
||||||
"Member System": "Member System",
|
"Member System": "Member System",
|
||||||
"Membership Tiers": "Membership Tiers",
|
"Membership Tiers": "Membership Tiers",
|
||||||
"Menu Permissions": "Menu Permissions",
|
"Menu Permissions": "Menu Permissions",
|
||||||
@ -1329,6 +1358,7 @@
|
|||||||
"Spring": "Spring",
|
"Spring": "Spring",
|
||||||
"Spring Channel Limit": "Spring Channel Limit",
|
"Spring Channel Limit": "Spring Channel Limit",
|
||||||
"Spring Limit": "Spring Limit",
|
"Spring Limit": "Spring Limit",
|
||||||
|
"Staff Card": "Staff Card",
|
||||||
"Staff Stock": "Staff Stock",
|
"Staff Stock": "Staff Stock",
|
||||||
"Standby": "Standby",
|
"Standby": "Standby",
|
||||||
"Standby Ad": "Standby Ad",
|
"Standby Ad": "Standby Ad",
|
||||||
|
|||||||
35
lang/ja.json
35
lang/ja.json
@ -200,6 +200,17 @@
|
|||||||
"Cancel Order": "注文取消",
|
"Cancel Order": "注文取消",
|
||||||
"Cancel Pass Code": "通行コードをキャンセル",
|
"Cancel Pass Code": "通行コードをキャンセル",
|
||||||
"Cancel Pickup 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",
|
"Cancel Purchase": "Cancel Purchase",
|
||||||
"Cancelled": "キャンセル済み",
|
"Cancelled": "キャンセル済み",
|
||||||
"Cannot Delete Role": "Cannot Delete Role",
|
"Cannot Delete Role": "Cannot Delete Role",
|
||||||
@ -228,6 +239,8 @@
|
|||||||
"Category Name (en)": "Category Name (en)",
|
"Category Name (en)": "Category Name (en)",
|
||||||
"Category Name (ja)": "Category Name (ja)",
|
"Category Name (ja)": "Category Name (ja)",
|
||||||
"Category Name (zh_TW)": "Category Name (zh_TW)",
|
"Category Name (zh_TW)": "Category Name (zh_TW)",
|
||||||
|
"E-Ticket (EasyCard/iPass)": "電子マネー (悠遊カード/iPass)",
|
||||||
|
"Easy Wallet": "悠遊付 (Easy Wallet)",
|
||||||
"Change": "Change",
|
"Change": "Change",
|
||||||
"Change Note": "Change Note",
|
"Change Note": "Change Note",
|
||||||
"Change Stock": "Change Stock",
|
"Change Stock": "Change Stock",
|
||||||
@ -250,6 +263,7 @@
|
|||||||
"Code Copied": "Code Copied",
|
"Code Copied": "Code Copied",
|
||||||
"Coin/Banknote Machine": "硬貨/紙幣モジュール",
|
"Coin/Banknote Machine": "硬貨/紙幣モジュール",
|
||||||
"Coin/Bill Acceptor": "コイン/紙幣機",
|
"Coin/Bill Acceptor": "コイン/紙幣機",
|
||||||
|
"Coin Acceptor": "硬貨識別機",
|
||||||
"Command Center": "Command Center",
|
"Command Center": "Command Center",
|
||||||
"Command Confirmation": "Command Confirmation",
|
"Command Confirmation": "Command Confirmation",
|
||||||
"Command Type": "Command Type",
|
"Command Type": "Command Type",
|
||||||
@ -621,7 +635,10 @@
|
|||||||
"LINE Pay Direct": "LINE Pay Direct",
|
"LINE Pay Direct": "LINE Pay Direct",
|
||||||
"LINE Pay Direct Settings Description": "LINE Pay Direct Settings Description",
|
"LINE Pay Direct Settings Description": "LINE Pay Direct Settings Description",
|
||||||
"LINE_MERCHANT_ID": "LINE_MERCHANT_ID",
|
"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 Communication": "Last Communication",
|
||||||
"Last Heartbeat": "Last Heartbeat",
|
"Last Heartbeat": "Last Heartbeat",
|
||||||
"Last Page": "Last Page",
|
"Last Page": "Last Page",
|
||||||
@ -769,12 +786,27 @@
|
|||||||
"Max Capacity": "最大容量",
|
"Max Capacity": "最大容量",
|
||||||
"Max Capacity:": "Max Capacity:",
|
"Max Capacity:": "Max Capacity:",
|
||||||
"Max Stock": "最大在庫",
|
"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": "Member",
|
||||||
"Member & External": "Member & External",
|
"Member & External": "Member & External",
|
||||||
"Member List": "Member List",
|
"Member List": "Member List",
|
||||||
"Member Management": "Member Management",
|
"Member Management": "Member Management",
|
||||||
"Member Price": "Member Price",
|
"Member Price": "Member Price",
|
||||||
"Member Status": "Member Status",
|
"Member Status": "Member Status",
|
||||||
|
"Member Verify Pickup": "会員認証受取",
|
||||||
"Member System": "Member System",
|
"Member System": "Member System",
|
||||||
"Membership Tiers": "Membership Tiers",
|
"Membership Tiers": "Membership Tiers",
|
||||||
"Menu Permissions": "Menu Permissions",
|
"Menu Permissions": "Menu Permissions",
|
||||||
@ -1323,6 +1355,7 @@
|
|||||||
"Spring": "スプリング",
|
"Spring": "スプリング",
|
||||||
"Spring Channel Limit": "Spring Channel Limit",
|
"Spring Channel Limit": "Spring Channel Limit",
|
||||||
"Spring Limit": "Spring Limit",
|
"Spring Limit": "Spring Limit",
|
||||||
|
"Staff Card": "スタッフカード",
|
||||||
"Staff Stock": "Staff Stock",
|
"Staff Stock": "Staff Stock",
|
||||||
"Standby": "Standby",
|
"Standby": "Standby",
|
||||||
"Standby Ad": "Standby Ad",
|
"Standby Ad": "Standby Ad",
|
||||||
|
|||||||
@ -81,6 +81,7 @@
|
|||||||
"Advertisement deleted successfully.": "廣告刪除成功。",
|
"Advertisement deleted successfully.": "廣告刪除成功。",
|
||||||
"Advertisement updated successfully": "廣告更新成功",
|
"Advertisement updated successfully": "廣告更新成功",
|
||||||
"Advertisement updated successfully.": "廣告更新成功。",
|
"Advertisement updated successfully.": "廣告更新成功。",
|
||||||
|
"Bill Acceptor": "紙鈔機",
|
||||||
"Affiliated Company": "所屬公司",
|
"Affiliated Company": "所屬公司",
|
||||||
"Affiliated Unit": "公司名稱",
|
"Affiliated Unit": "公司名稱",
|
||||||
"Affiliation": "所屬單位",
|
"Affiliation": "所屬單位",
|
||||||
@ -257,6 +258,7 @@
|
|||||||
"Code Copied": "代碼已複製",
|
"Code Copied": "代碼已複製",
|
||||||
"Coin/Banknote Machine": "硬幣機/紙鈔機",
|
"Coin/Banknote Machine": "硬幣機/紙鈔機",
|
||||||
"Coin/Bill Acceptor": "硬幣機/紙鈔機",
|
"Coin/Bill Acceptor": "硬幣機/紙鈔機",
|
||||||
|
"Coin Acceptor": "硬幣機",
|
||||||
"Command Center": "指令中心",
|
"Command Center": "指令中心",
|
||||||
"Command Confirmation": "指令確認",
|
"Command Confirmation": "指令確認",
|
||||||
"Command Type": "指令類型",
|
"Command Type": "指令類型",
|
||||||
@ -470,6 +472,8 @@
|
|||||||
"Edit Staff Card": "編輯員工識別卡",
|
"Edit Staff Card": "編輯員工識別卡",
|
||||||
"Edit Sub Account Role": "編輯子帳號角色",
|
"Edit Sub Account Role": "編輯子帳號角色",
|
||||||
"Edit Warehouse": "編輯倉庫",
|
"Edit Warehouse": "編輯倉庫",
|
||||||
|
"E-Ticket (EasyCard/iPass)": "電子票證 (悠遊卡/一卡通)",
|
||||||
|
"Easy Wallet": "悠遊付",
|
||||||
"Electronic Invoice": "電子發票",
|
"Electronic Invoice": "電子發票",
|
||||||
"Electronic Invoices": "電子發票",
|
"Electronic Invoices": "電子發票",
|
||||||
"Elevator descending": "升降平台下降中",
|
"Elevator descending": "升降平台下降中",
|
||||||
@ -712,6 +716,7 @@
|
|||||||
"Issued At": "開立時間",
|
"Issued At": "開立時間",
|
||||||
"Item List": "商品清單",
|
"Item List": "商品清單",
|
||||||
"Items": "筆",
|
"Items": "筆",
|
||||||
|
"JKO Pay": "街口支付",
|
||||||
"JKO_MERCHANT_ID": "街口支付 商店代號",
|
"JKO_MERCHANT_ID": "街口支付 商店代號",
|
||||||
"Japanese": "日文",
|
"Japanese": "日文",
|
||||||
"Inactive": "已停用",
|
"Inactive": "已停用",
|
||||||
@ -722,7 +727,8 @@
|
|||||||
"LEVEL TYPE": "層級類型",
|
"LEVEL TYPE": "層級類型",
|
||||||
"LINE Pay Direct": "LINE Pay 官方直連",
|
"LINE Pay Direct": "LINE Pay 官方直連",
|
||||||
"LINE Pay Direct Settings Description": "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": "實時",
|
"LIVE": "實時",
|
||||||
"Last Communication": "最後通訊",
|
"Last Communication": "最後通訊",
|
||||||
"Last Heartbeat": "最後心跳時間",
|
"Last Heartbeat": "最後心跳時間",
|
||||||
@ -882,7 +888,22 @@
|
|||||||
"Member List": "會員列表",
|
"Member List": "會員列表",
|
||||||
"Member Management": "會員管理",
|
"Member Management": "會員管理",
|
||||||
"Member Price": "會員價",
|
"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 Status": "會員狀態",
|
||||||
|
"Member Verify Pickup": "會員驗證取貨",
|
||||||
"Member System": "會員系統",
|
"Member System": "會員系統",
|
||||||
"Membership Tiers": "會員等級",
|
"Membership Tiers": "會員等級",
|
||||||
"Menu Permissions": "選單權限",
|
"Menu Permissions": "選單權限",
|
||||||
@ -1151,10 +1172,22 @@
|
|||||||
"Please select a material": "請選擇素材",
|
"Please select a material": "請選擇素材",
|
||||||
"Please select a slot": "請選擇貨道",
|
"Please select a slot": "請選擇貨道",
|
||||||
"Please select warehouse and machine": "請選擇倉庫和機台",
|
"Please select warehouse and machine": "請選擇倉庫和機台",
|
||||||
|
"Pi Pay": "Pi 拍錢包",
|
||||||
|
"PlusPay": "全盈+PAY",
|
||||||
"Point Rules": "點數規則",
|
"Point Rules": "點數規則",
|
||||||
"Point Settings": "點數設定",
|
"Point Settings": "點數設定",
|
||||||
"Points": "點數功能",
|
"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 Settings": "點數設定",
|
||||||
"Points toggle": "點數開關",
|
"Points toggle": "點數開關",
|
||||||
"Position": "投放位置",
|
"Position": "投放位置",
|
||||||
@ -1206,6 +1239,7 @@
|
|||||||
"Qty Change": "異動數量",
|
"Qty Change": "異動數量",
|
||||||
"Quality": "品質 (Quality)",
|
"Quality": "品質 (Quality)",
|
||||||
"Quantity": "數量",
|
"Quantity": "數量",
|
||||||
|
"QR Code Payment": "掃碼支付",
|
||||||
"Questionnaire": "問卷",
|
"Questionnaire": "問卷",
|
||||||
"Quick Expiry Check": "效期快速檢查",
|
"Quick Expiry Check": "效期快速檢查",
|
||||||
"Quick Maintenance": "快速維護",
|
"Quick Maintenance": "快速維護",
|
||||||
@ -1444,6 +1478,7 @@
|
|||||||
"Spring Channel Limit": "彈簧貨道上限",
|
"Spring Channel Limit": "彈簧貨道上限",
|
||||||
"Spring Limit": "彈簧貨道上限",
|
"Spring Limit": "彈簧貨道上限",
|
||||||
"Staff": "員工",
|
"Staff": "員工",
|
||||||
|
"Staff Card": "員工卡",
|
||||||
"Staff Identification Management": "員工識別管理",
|
"Staff Identification Management": "員工識別管理",
|
||||||
"Staff List": "員工列表",
|
"Staff List": "員工列表",
|
||||||
"Staff Name": "員工姓名",
|
"Staff Name": "員工姓名",
|
||||||
|
|||||||
@ -1,39 +1,35 @@
|
|||||||
{{-- Search Bar --}}
|
{{-- Search Bar --}}
|
||||||
<form action="{{ route('admin.sales.index') }}" method="GET"
|
<form action="{{ route('admin.sales.index') }}" method="GET"
|
||||||
class="flex flex-col lg:flex-row lg:items-center flex-wrap gap-3 mb-8"
|
class="flex flex-col lg:flex-row lg:items-center flex-wrap gap-3 mb-8" @submit.prevent="fetchTabData('dispense')">
|
||||||
@submit.prevent="fetchTabData('dispense')">
|
|
||||||
|
|
||||||
<input type="hidden" name="tab" value="dispense">
|
<input type="hidden" name="tab" value="dispense">
|
||||||
|
|
||||||
<div class="relative group flex-1 min-w-[280px]">
|
<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">
|
<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]"
|
<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"
|
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
||||||
stroke-linejoin="round">
|
|
||||||
<circle cx="11" cy="11" r="8"></circle>
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<input type="text" name="search" value="{{ $tab === 'dispense' ? $filters['search'] : '' }}"
|
<input type="text" name="search" value="{{ $tab === 'dispense' ? $filters['search'] : '' }}"
|
||||||
class="py-2.5 pl-12 pr-6 block w-full luxury-input"
|
class="py-2.5 pl-12 pr-6 block w-full luxury-input" placeholder="{{ __('Search Product / Slot...') }}">
|
||||||
placeholder="{{ __('Search Product / Slot...') }}">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full lg:w-48">
|
<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'))">
|
@change="$el.closest('form').dispatchEvent(new Event('submit'))">
|
||||||
@foreach($machines as $m)
|
@foreach($machines as $m)
|
||||||
<option value="{{ $m->id }}" {{ $filters['machine_id'] == $m->id ? 'selected' : '' }} data-title="{{ $m->name }}">
|
<option value="{{ $m->id }}" {{ $filters['machine_id']==$m->id ? 'selected' : '' }} data-title="{{ $m->name
|
||||||
{{ $m->name }}
|
}}">
|
||||||
</option>
|
{{ $m->name }}
|
||||||
|
</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</x-searchable-select>
|
</x-searchable-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- 日期範圍 --}}
|
{{-- 日期範圍 --}}
|
||||||
<div class="relative group w-full lg:w-72 lg:flex-none"
|
<div class="relative group w-full lg:w-72 lg:flex-none" x-data="{ fp: null }" x-init="fp = flatpickr($refs.dateRange, {
|
||||||
x-data="{ fp: null }"
|
|
||||||
x-init="fp = flatpickr($refs.dateRange, {
|
|
||||||
mode: 'range',
|
mode: 'range',
|
||||||
dateFormat: 'Y-m-d H:i',
|
dateFormat: 'Y-m-d H:i',
|
||||||
enableTime: true,
|
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">
|
<span
|
||||||
<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>
|
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>
|
</span>
|
||||||
<input type="hidden" name="start_date" x-ref="startDate" value="{{ $filters['start_date'] }}">
|
<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="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'] ?: '') }}"
|
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>
|
||||||
|
|
||||||
<div class="flex items-center gap-2 ml-auto lg:ml-0 shrink-0">
|
<div class="flex items-center gap-2 ml-auto lg:ml-0 shrink-0">
|
||||||
<button type="submit"
|
<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"
|
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') }}">
|
title="{{ __('Search') }}">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||||
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" />
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button type="button" @click="switchTab('dispense', '{{ route('admin.sales.index', ['tab' => 'dispense']) }}')"
|
||||||
@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"
|
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') }}">
|
title="{{ __('Reset') }}">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||||
stroke-width="2.5">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
<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" />
|
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>
|
</svg>
|
||||||
@ -92,88 +89,93 @@
|
|||||||
<table class="w-full text-left border-separate border-spacing-0">
|
<table class="w-full text-left border-separate border-spacing-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
<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') }}
|
{{ __('Machine') }}
|
||||||
</th>
|
</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') }}
|
{{ __('Product / Slot') }}
|
||||||
</th>
|
</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') }}
|
{{ __('Amount') }}
|
||||||
</th>
|
</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') }}
|
{{ __('Dispense Status') }}
|
||||||
</th>
|
</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') }}
|
{{ __('Dispense Time') }}
|
||||||
</th>
|
</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') }}
|
{{ __('Associated Order') }}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-slate-100 dark:divide-slate-800/80">
|
<tbody class="divide-y divide-slate-100 dark:divide-slate-800/80">
|
||||||
@forelse($dispenseLogs as $log)
|
@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 }})">
|
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200 {{ $log->order_id ? 'cursor-pointer' : '' }}"
|
||||||
<td class="px-6 py-6">
|
@click="if({{ $log->order_id ?? 'null' }}) openDetail({{ $log->order_id }})">
|
||||||
<div class="text-sm font-extrabold text-slate-800 dark:text-slate-100 tracking-tight">
|
<td class="px-6 py-6">
|
||||||
{{ $log->machine->name ?? 'Unknown' }}
|
<div class="text-sm font-extrabold text-slate-800 dark:text-slate-100 tracking-tight">
|
||||||
</div>
|
{{ $log->machine->name ?? 'Unknown' }}
|
||||||
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em] mt-1">
|
</div>
|
||||||
{{ $log->machine->serial_no ?? '---' }}
|
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em] mt-1">
|
||||||
</div>
|
{{ $log->machine->serial_no ?? '---' }}
|
||||||
</td>
|
</div>
|
||||||
<td class="px-6 py-6">
|
</td>
|
||||||
<div class="text-sm font-black text-cyan-600 dark:text-cyan-400 mb-0.5">
|
<td class="px-6 py-6">
|
||||||
{{ $log->product->name ?? 'Unknown Product' }}
|
<div class="text-sm font-black text-cyan-600 dark:text-cyan-400 mb-0.5">
|
||||||
</div>
|
{{ $log->product->name ?? 'Unknown Product' }}
|
||||||
<div class="text-xs font-bold text-slate-400 uppercase tracking-widest">
|
</div>
|
||||||
{{ __('Slot') }}: {{ $log->slot_no }}
|
<div class="text-xs font-bold text-slate-400 uppercase tracking-widest">
|
||||||
</div>
|
{{ __('Slot') }}: {{ $log->slot_no }}
|
||||||
</td>
|
</div>
|
||||||
<td class="px-6 py-6">
|
</td>
|
||||||
<span class="text-sm font-black text-slate-700 dark:text-slate-200">
|
<td class="px-6 py-6">
|
||||||
{{ $log->amount }}
|
<span class="text-sm font-black text-slate-700 dark:text-slate-200">
|
||||||
</span>
|
{{ $log->amount }}
|
||||||
</td>
|
</span>
|
||||||
<td class="px-6 py-6 whitespace-nowrap">
|
</td>
|
||||||
@php
|
<td class="px-6 py-6 whitespace-nowrap">
|
||||||
$statusMap = [
|
@php
|
||||||
'success' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
|
$statusMap = [
|
||||||
'failed' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
|
'success' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
|
||||||
'pending' => ['label' => __('Dispense Pending'), 'color' => 'amber'],
|
'failed' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
|
||||||
'1' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
|
'pending' => ['label' => __('Dispense Pending'), 'color' => 'amber'],
|
||||||
'0' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
|
'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'];
|
$statusKey = (string)($log->dispense_status ?? 'pending');
|
||||||
@endphp
|
$s = $statusMap[$statusKey] ?? ['label' => $statusKey, 'color' => 'slate'];
|
||||||
<x-status-badge :color="$s['color']" :label="$s['label']" size="xs" />
|
@endphp
|
||||||
</td>
|
<x-status-badge :color="$s['color']" :label="$s['label']" size="xs" />
|
||||||
<td class="px-6 py-6">
|
</td>
|
||||||
<span class="text-sm font-black text-slate-700 dark:text-slate-200">
|
<td class="px-6 py-6">
|
||||||
{{ $log->machine_time }}
|
<span class="text-sm font-black text-slate-700 dark:text-slate-200">
|
||||||
</span>
|
{{ $log->machine_time }}
|
||||||
</td>
|
</span>
|
||||||
<td class="px-6 py-6 text-right">
|
</td>
|
||||||
@if($log->order)
|
<td class="px-6 py-6 text-right">
|
||||||
<button
|
@if($log->order)
|
||||||
@click.stop="openDetail({{ $log->order_id }})"
|
<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"
|
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 }}
|
||||||
{{ $log->order->order_no }}
|
</button>
|
||||||
</button>
|
@else
|
||||||
@else
|
<span class="text-xs text-slate-300 italic">{{ __('No associated order found') }}</span>
|
||||||
<span class="text-xs text-slate-300 italic">{{ __('No associated order found') }}</span>
|
@endif
|
||||||
@endif
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="py-20 text-center">
|
<td colspan="6" class="py-20 text-center">
|
||||||
<x-empty-state mode="table" colspan="6" :message="__('No dispense records found')" />
|
<x-empty-state mode="table" colspan="6" :message="__('No dispense records found')" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforelse
|
@endforelse
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -182,89 +184,100 @@
|
|||||||
{{-- Mobile Cards (xl:hidden) --}}
|
{{-- Mobile Cards (xl:hidden) --}}
|
||||||
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
@forelse($dispenseLogs as $log)
|
@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">
|
<div
|
||||||
@php
|
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">
|
||||||
$statusMap = [
|
@php
|
||||||
'success' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
|
$statusMap = [
|
||||||
'failed' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
|
'success' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
|
||||||
'pending' => ['label' => __('Dispense Pending'), 'color' => 'amber'],
|
'failed' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
|
||||||
'1' => ['label' => __('Dispense Success'), 'color' => 'emerald'],
|
'pending' => ['label' => __('Dispense Pending'), 'color' => 'amber'],
|
||||||
'0' => ['label' => __('Dispense Failed'), 'color' => 'rose'],
|
'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'];
|
$statusKey = (string)($log->dispense_status ?? 'pending');
|
||||||
@endphp
|
$s = $statusMap[$statusKey] ?? ['label' => $statusKey, 'color' => 'slate'];
|
||||||
|
@endphp
|
||||||
|
|
||||||
{{-- Card Header --}}
|
{{-- Card Header --}}
|
||||||
<div class="flex items-start justify-between gap-4 mb-6">
|
<div class="flex items-start justify-between gap-4 mb-6">
|
||||||
<div class="flex items-center gap-4 min-w-0">
|
<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">
|
<div
|
||||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
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">
|
||||||
<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 class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
</svg>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
</div>
|
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" />
|
||||||
<div class="min-w-0">
|
</svg>
|
||||||
<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>
|
</div>
|
||||||
<x-status-badge :color="$s['color']" :label="$s['label']" size="sm" />
|
<div class="min-w-0">
|
||||||
</div>
|
<h3 class="text-base font-extrabold text-slate-800 dark:text-slate-100 truncate tracking-tight">
|
||||||
|
{{ $log->machine->name ?? 'Unknown' }}
|
||||||
{{-- Info Grid --}}
|
</h3>
|
||||||
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
|
<p
|
||||||
<div>
|
class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest truncate">
|
||||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Product / Slot') }}</p>
|
{{ $log->machine->serial_no ?? '---' }}
|
||||||
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<x-status-badge :color="$s['color']" :label="$s['label']" size="sm" />
|
||||||
|
</div>
|
||||||
|
|
||||||
{{-- Action Buttons --}}
|
{{-- Info Grid --}}
|
||||||
<div class="flex items-center gap-3">
|
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
|
||||||
@if($log->order_id)
|
<div>
|
||||||
<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">
|
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
__('Product / Slot') }}</p>
|
||||||
<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" />
|
<div class="flex flex-col gap-0.5 min-w-0">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
<p class="text-sm font-black text-cyan-600 dark:text-cyan-400 truncate">
|
||||||
</svg>
|
{{ $log->product->name ?? 'Unknown' }}
|
||||||
{{ __('View Order') }}
|
</p>
|
||||||
</button>
|
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">
|
||||||
@else
|
{{ __('Slot') }}: {{ $log->slot_no }}
|
||||||
<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">
|
</p>
|
||||||
{{ __('No related detail found') }}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
<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>
|
||||||
</div>
|
</div>
|
||||||
@empty
|
|
||||||
<div class="col-span-full py-10">
|
{{-- Action Buttons --}}
|
||||||
<x-empty-state :message="__('No dispense records found')" />
|
<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>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div class="col-span-full py-10">
|
||||||
|
<x-empty-state :message="__('No dispense records found')" />
|
||||||
|
</div>
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,13 @@
|
|||||||
{{-- Search Bar --}}
|
{{-- Search Bar --}}
|
||||||
<form action="{{ route('admin.sales.index') }}" method="GET"
|
<form action="{{ route('admin.sales.index') }}" method="GET"
|
||||||
class="flex flex-col lg:flex-row lg:items-center flex-wrap gap-3 mb-8"
|
class="flex flex-col lg:flex-row lg:items-center flex-wrap gap-3 mb-8" @submit.prevent="fetchTabData('invoices')">
|
||||||
@submit.prevent="fetchTabData('invoices')">
|
|
||||||
|
|
||||||
<input type="hidden" name="tab" value="invoices">
|
<input type="hidden" name="tab" value="invoices">
|
||||||
|
|
||||||
<div class="relative group flex-1 min-w-[280px]">
|
<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">
|
<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]"
|
<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"
|
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
||||||
stroke-linejoin="round">
|
|
||||||
<circle cx="11" cy="11" r="8"></circle>
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||||
</svg>
|
</svg>
|
||||||
@ -23,17 +21,16 @@
|
|||||||
<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'))">
|
@change="$el.closest('form').dispatchEvent(new Event('submit'))">
|
||||||
@foreach($machines as $m)
|
@foreach($machines as $m)
|
||||||
<option value="{{ $m->id }}" {{ $filters['machine_id'] == $m->id ? 'selected' : '' }} data-title="{{ $m->name }}">
|
<option value="{{ $m->id }}" {{ $filters['machine_id']==$m->id ? 'selected' : '' }} data-title="{{ $m->name
|
||||||
{{ $m->name }}
|
}}">
|
||||||
</option>
|
{{ $m->name }}
|
||||||
|
</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</x-searchable-select>
|
</x-searchable-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- 日期範圍 --}}
|
{{-- 日期範圍 --}}
|
||||||
<div class="relative group w-full lg:w-72 lg:flex-none"
|
<div class="relative group w-full lg:w-72 lg:flex-none" x-data="{ fp: null }" x-init="fp = flatpickr($refs.dateRange, {
|
||||||
x-data="{ fp: null }"
|
|
||||||
x-init="fp = flatpickr($refs.dateRange, {
|
|
||||||
mode: 'range',
|
mode: 'range',
|
||||||
dateFormat: 'Y-m-d H:i',
|
dateFormat: 'Y-m-d H:i',
|
||||||
enableTime: true,
|
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">
|
<span
|
||||||
<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>
|
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>
|
</span>
|
||||||
<input type="hidden" name="start_date" x-ref="startDate" value="{{ $filters['start_date'] }}">
|
<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="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'] ?: '') }}"
|
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>
|
||||||
|
|
||||||
<div class="flex items-center gap-2 ml-auto lg:ml-0 shrink-0">
|
<div class="flex items-center gap-2 ml-auto lg:ml-0 shrink-0">
|
||||||
<button type="submit"
|
<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"
|
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') }}">
|
title="{{ __('Search') }}">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||||
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" />
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button type="button" @click="switchTab('invoices', '{{ route('admin.sales.index', ['tab' => 'invoices']) }}')"
|
||||||
@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"
|
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') }}">
|
title="{{ __('Reset') }}">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||||
stroke-width="2.5">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
<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" />
|
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>
|
</svg>
|
||||||
@ -92,97 +90,111 @@
|
|||||||
<table class="w-full text-left border-separate border-spacing-0">
|
<table class="w-full text-left border-separate border-spacing-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
<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') }}
|
{{ __('Invoice Number') }}
|
||||||
</th>
|
</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') }}
|
{{ __('Invoice Date') }}
|
||||||
</th>
|
</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') }}
|
{{ __('Machine') }}
|
||||||
</th>
|
</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') }}
|
{{ __('Amount') }}
|
||||||
</th>
|
</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') }}
|
{{ __('Status') }}
|
||||||
</th>
|
</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') }}
|
{{ __('Associated Order') }}
|
||||||
</th>
|
</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') }}
|
{{ __('Action') }}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-slate-100 dark:divide-slate-800/80">
|
<tbody class="divide-y divide-slate-100 dark:divide-slate-800/80">
|
||||||
@forelse($invoices as $invoice)
|
@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 }})">
|
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200 cursor-pointer"
|
||||||
<td class="px-6 py-6">
|
@click="openDetail({{ $invoice->order_id }})">
|
||||||
<div class="flex flex-col">
|
<td class="px-6 py-6">
|
||||||
<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">
|
<div class="flex flex-col">
|
||||||
{{ $invoice->invoice_no }}
|
<span
|
||||||
</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">
|
||||||
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1">
|
{{ $invoice->invoice_no }}
|
||||||
{{ ($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 }}
|
|
||||||
</span>
|
</span>
|
||||||
</td>
|
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1">
|
||||||
<td class="px-6 py-6">
|
{{ ($invoice->machine_time ?? $invoice->created_at)->format('Y-m-d H:i:s') }}
|
||||||
<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>
|
</span>
|
||||||
</td>
|
</div>
|
||||||
<td class="px-6 py-6 whitespace-nowrap">
|
</td>
|
||||||
@php
|
<td class="px-6 py-6">
|
||||||
// 簡單判斷:如果有發票號碼且 rtn_code 為空或 0/1 則視為有效
|
<span
|
||||||
$status = 'valid';
|
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">
|
||||||
if (empty($invoice->invoice_no)) $status = 'failed';
|
{{ $invoice->invoice_date }}
|
||||||
if ($invoice->rtn_code === 'void') $status = 'void';
|
</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 = [
|
$statusMap = [
|
||||||
'valid' => ['label' => __('Valid'), 'color' => 'emerald'],
|
'valid' => ['label' => __('Valid'), 'color' => 'emerald'],
|
||||||
'void' => ['label' => __('Void'), 'color' => 'rose'],
|
'void' => ['label' => __('Void'), 'color' => 'rose'],
|
||||||
'failed' => ['label' => __('Failed'), 'color' => 'rose'],
|
'failed' => ['label' => __('Failed'), 'color' => 'rose'],
|
||||||
'refunded' => ['label' => __('Refunded'), 'color' => 'slate'],
|
'refunded' => ['label' => __('Refunded'), 'color' => 'slate'],
|
||||||
];
|
];
|
||||||
$s = $statusMap[$status] ?? ['label' => $status, 'color' => 'slate'];
|
$s = $statusMap[$status] ?? ['label' => $status, 'color' => 'slate'];
|
||||||
@endphp
|
@endphp
|
||||||
<x-status-badge :color="$s['color']" :label="$s['label']" size="xs" />
|
<x-status-badge :color="$s['color']" :label="$s['label']" size="xs" />
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-6">
|
<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">
|
<span
|
||||||
{{ $invoice->order->order_no ?? '---' }}
|
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">
|
||||||
</span>
|
{{ $invoice->order->order_no ?? '---' }}
|
||||||
</td>
|
</span>
|
||||||
<td class="px-6 py-6 text-right">
|
</td>
|
||||||
<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">
|
<td class="px-6 py-6 text-right">
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<button
|
||||||
<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" />
|
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">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
</svg>
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
</button>
|
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" />
|
||||||
</td>
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
</tr>
|
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="py-20 text-center">
|
<td colspan="6" class="py-20 text-center">
|
||||||
<x-empty-state mode="table" colspan="6" :message="__('No invoices found')" />
|
<x-empty-state mode="table" colspan="6" :message="__('No invoices found')" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforelse
|
@endforelse
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -191,89 +203,102 @@
|
|||||||
{{-- Mobile Cards (xl:hidden) --}}
|
{{-- Mobile Cards (xl:hidden) --}}
|
||||||
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
@forelse($invoices as $invoice)
|
@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">
|
<div
|
||||||
@php
|
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">
|
||||||
$status = 'valid';
|
@php
|
||||||
if (empty($invoice->invoice_no)) $status = 'failed';
|
$status = 'valid';
|
||||||
if ($invoice->rtn_code === 'void') $status = 'void';
|
if (empty($invoice->invoice_no)) $status = 'failed';
|
||||||
$statusMap = [
|
if ($invoice->rtn_code === 'void') $status = 'void';
|
||||||
'valid' => ['label' => __('Valid'), 'color' => 'emerald'],
|
$statusMap = [
|
||||||
'void' => ['label' => __('Void'), 'color' => 'rose'],
|
'valid' => ['label' => __('Valid'), 'color' => 'emerald'],
|
||||||
'failed' => ['label' => __('Failed'), 'color' => 'rose'],
|
'void' => ['label' => __('Void'), 'color' => 'rose'],
|
||||||
'refunded' => ['label' => __('Refunded'), 'color' => 'slate'],
|
'failed' => ['label' => __('Failed'), 'color' => 'rose'],
|
||||||
];
|
'refunded' => ['label' => __('Refunded'), 'color' => 'slate'],
|
||||||
$s = $statusMap[$status] ?? ['label' => $status, 'color' => 'slate'];
|
];
|
||||||
@endphp
|
$s = $statusMap[$status] ?? ['label' => $status, 'color' => 'slate'];
|
||||||
|
@endphp
|
||||||
|
|
||||||
{{-- Card Header --}}
|
{{-- Card Header --}}
|
||||||
<div class="flex items-start justify-between gap-4 mb-6">
|
<div class="flex items-start justify-between gap-4 mb-6">
|
||||||
<div class="flex items-center gap-4 min-w-0">
|
<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">
|
<div
|
||||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
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">
|
||||||
<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 class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
</svg>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
</div>
|
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" />
|
||||||
<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" />
|
|
||||||
</svg>
|
</svg>
|
||||||
{{ __('View Details') }}
|
</div>
|
||||||
</button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
@empty
|
|
||||||
<div class="col-span-full py-10">
|
{{-- Action Buttons --}}
|
||||||
<x-empty-state :message="__('No invoices found')" />
|
<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>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div class="col-span-full py-10">
|
||||||
|
<x-empty-state :message="__('No invoices found')" />
|
||||||
|
</div>
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -279,7 +279,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Info Grid --}}
|
{{-- 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>
|
<div>
|
||||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
|
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
|
||||||
__('Order Items') }}</p>
|
__('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">{{
|
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
|
||||||
__('Order Time') }}</p>
|
__('Order Time') }}</p>
|
||||||
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">
|
<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>
|
<span class="text-[10px] text-slate-400 ml-1">{{ $order->created_at->diffForHumans() }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -110,35 +110,42 @@
|
|||||||
window.location.reload();
|
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 -->
|
<!-- 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)"
|
: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">
|
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 -->
|
<!-- Progress Ring -->
|
||||||
<svg class="absolute inset-0 size-full -rotate-90" viewBox="0 0 24 24">
|
<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"
|
<circle class="text-cyan-500 transition-all duration-1000 ease-linear" stroke-width="2"
|
||||||
:stroke-dasharray="circumference"
|
:stroke-dasharray="circumference"
|
||||||
:stroke-dashoffset="circumference - (interval === 'Off' ? 0 : ((parseInt(interval) - timeLeft) / parseInt(interval)) * 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"
|
stroke-linecap="round" stroke="currentColor" fill="transparent" r="9" cx="12" cy="12"
|
||||||
x-show="interval !== 'Off'"/>
|
x-show="interval !== 'Off'" />
|
||||||
</svg>
|
</svg>
|
||||||
<!-- Refresh Icon -->
|
<!-- 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">
|
<svg class="size-4 relative z-10" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
||||||
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
|
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
<path d="M21 3v5h-5"/>
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
<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 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
|
||||||
<path d="M3 21v-5h5"/>
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Interval Selector -->
|
<!-- Interval Selector -->
|
||||||
<button type="button" @click="refreshOpen = !refreshOpen" @click.away="refreshOpen = false"
|
<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">
|
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>
|
<span class="hidden md:inline whitespace-nowrap"
|
||||||
<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">
|
x-text="interval === 'Off' ? '{{ __('Off') }}' : interval"></span>
|
||||||
<path d="m6 9 6 6 6-6"/>
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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="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' : ''">
|
: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>
|
<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" />
|
<polyline points="20 6 9 17 4 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
@ -348,8 +358,7 @@
|
|||||||
:class="[
|
:class="[
|
||||||
sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0',
|
sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0',
|
||||||
sidebarCollapsed ? 'w-[60px]' : 'w-56'
|
sidebarCollapsed ? 'w-[60px]' : 'w-56'
|
||||||
]"
|
]" x-init="$nextTick(() => {
|
||||||
x-init="$nextTick(() => {
|
|
||||||
const activeItem = $el.querySelector('.active, .bg-slate-100, .dark\\:bg-white\\/5');
|
const activeItem = $el.querySelector('.active, .bg-slate-100, .dark\\:bg-white\\/5');
|
||||||
if (activeItem) {
|
if (activeItem) {
|
||||||
activeItem.scrollIntoView({ block: 'center', behavior: 'instant' });
|
activeItem.scrollIntoView({ block: 'center', behavior: 'instant' });
|
||||||
|
|||||||
@ -60,7 +60,6 @@ Route::prefix('v1')->middleware(['throttle:api'])->group(function () {
|
|||||||
// 機台狀態與相關同步 (B017, B710, B220)
|
// 機台狀態與相關同步 (B017, B710, B220)
|
||||||
|
|
||||||
Route::get('machine/reload_msg/B017', [App\Http\Controllers\Api\V1\App\MachineController::class, 'getSlots']);
|
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/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']);
|
Route::post('machine/member/verify/B650', [App\Http\Controllers\Api\V1\App\MachineController::class, 'verifyMember']);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user