[STYLE] 銷售中心 UI 優化與出貨紀錄邏輯校正

1. 優化「出貨紀錄」介面,重新排列欄位優先級並移除冗餘的機台流水號。
2. 統一出貨紀錄中的商品與貨道樣式,與取貨碼模組視覺語言同步。
3. 修正出貨紀錄搜尋邏輯,支援商品名稱與貨道編號模糊搜尋。
4. 修正「訂單詳情」側滑面板中的金額顯示(修正 price/subtotal 映射)。
5. 補強訂單詳情的出貨狀態 (dispense_status) 標籤顯示邏輯。
6. 將出貨紀錄中的「數量」表頭修正為「金額」,確保與 MQTT 協議回傳資料語意一致。
7. 更新多語系檔,新增搜尋欄位 placeholder 與相關 UI 標籤翻譯。
8. 新增發票資料表 machine_time 欄位遷移檔,以完整記錄機台開立時間。
This commit is contained in:
sky121113 2026-05-04 17:20:43 +08:00
parent e779880f9b
commit 023f639fb4
23 changed files with 2003 additions and 147 deletions

View File

@ -181,6 +181,7 @@ Mqtt3Client client = MqttClient.builder()
"flow_id": "T202604140001",
"invoice_no": "AB12345678",
"invoice_date": "2026-04-22",
"machine_time": "2026-04-22 14:30:15",
"amount": 25.0,
"random_no": "1234",
"love_code": ""
@ -234,6 +235,7 @@ Mqtt3Client client = MqttClient.builder()
"invoice": {
"invoice_no": "AB12345678",
"invoice_date": "2025-08-08",
"machine_time": "2025-08-08 10:01:30",
"amount": 100,
"random_number": "1234",
"love_code": "",

View File

@ -126,7 +126,17 @@ class ListenMqttQueue extends Command
'payload' => $payload
]);
$action = $payload['action'] ?? 'create';
$rawAction = $payload['action'] ?? 'create';
$action = strtolower(trim($rawAction));
// 超級偵錯:檢查 action 字串的每一個位元組
$hex = bin2hex($rawAction);
Log::debug("MQTT Action Debug", [
'raw' => $rawAction,
'hex' => $hex,
'processed' => $action,
'match_finalize' => ($action === 'finalize' ? 'YES' : 'NO')
]);
switch ($action) {
case 'invoice':

View File

@ -4,28 +4,152 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Transaction\Order;
use App\Models\Transaction\Invoice;
use App\Models\Transaction\DispenseRecord;
use App\Models\Transaction\PaymentType;
use App\Models\Transaction\OrderItem;
use App\Models\Transaction\PickupCode;
use App\Models\Transaction\PassCode;
use App\Models\Machine\Machine;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use App\Models\System\SystemOperationLog;
class SalesController extends Controller
{
// 銷售&金流紀錄
public function index()
// 銷售中心 (銷售&金流紀錄)
public function index(Request $request)
{
return view('admin.placeholder', [
$tab = $request->input('tab', 'orders');
$isAjax = $request->ajax();
// 取得篩選參數
$search = $request->input('search');
$machineId = $request->input('machine_id');
$paymentType = $request->input('payment_type');
$status = $request->input('status');
$startDate = $request->input('start_date');
$endDate = $request->input('end_date');
$data = [
'title' => '銷售&金流紀錄',
'description' => '銷售交易與金流明細查詢',
'features' => [
'銷售記錄查詢',
'金流對帳',
'發票管理',
'退款處理',
]
'tab' => $tab,
'filters' => [
'search' => $search,
'machine_id' => $machineId,
'payment_type' => $paymentType,
'status' => $status,
'start_date' => $startDate,
'end_date' => $endDate,
],
'machines' => Machine::select('id', 'name', 'serial_no')->get(),
'paymentTypes' => [
1 => '信用卡',
2 => '電子票證',
3 => '掃碼支付',
4 => '紙鈔機',
9 => '零錢',
30 => 'LINE Pay',
31 => '街口支付',
32 => '悠遊付',
33 => 'Pi 拍錢包',
34 => '全盈+PAY',
60 => '點數/優惠券',
],
];
// 1. 建立基本查詢 (套用共用過濾器:機台、日期)
$ordersQuery = Order::with(['machine:id,name,serial_no', 'invoice:id,order_id,invoice_no', 'items']);
$invoicesQuery = Invoice::with(['machine:id,name,serial_no', 'order:id,order_no,flow_id,payment_type']);
$dispenseQuery = DispenseRecord::with(['order:id,order_no,flow_id', 'machine:id,name,serial_no', 'product:id,name']);
// 共用過濾器:日期
if ($startDate && $endDate) {
$start = Carbon::parse($startDate);
$end = Carbon::parse($endDate)->endOfMinute();
$ordersQuery->whereBetween('created_at', [$start, $end]);
$invoicesQuery->whereBetween('invoice_date', [$start, $end]);
$dispenseQuery->whereBetween('machine_time', [$start, $end]);
}
// 共用過濾器:機台
if ($machineId) {
$ordersQuery->where('machine_id', $machineId);
$invoicesQuery->where('machine_id', $machineId);
$dispenseQuery->where('machine_id', $machineId);
}
// 2. 應用獨立過濾器:搜尋 (僅對當前 Tab 應用)
if ($search) {
if ($tab === 'orders') {
$ordersQuery->where(function($q) use ($search) {
$q->where('order_no', 'like', "%{$search}%")
->orWhere('flow_id', 'like', "%{$search}%")
->orWhere('invoice_info', 'like', "%{$search}%")
->orWhere('member_barcode', 'like', "%{$search}%");
});
} elseif ($tab === 'invoices') {
$invoicesQuery->where(function($q) use ($search) {
$q->where('invoice_no', 'like', "%{$search}%")
->orWhere('flow_id', 'like', "%{$search}%");
});
} elseif ($tab === 'dispense') {
$dispenseQuery->where(function($q) use ($search) {
$q->where('slot_no', 'like', "%{$search}%")
->orWhereHas('product', function($pq) use ($search) {
$pq->where('name', 'like', "%{$search}%");
})
->orWhereHas('order', function($oq) use ($search) {
$oq->where('order_no', 'like', "%{$search}%");
});
});
}
}
// 訂單專用過濾器
if ($tab === 'orders') {
if ($paymentType) $ordersQuery->where('payment_type', $paymentType);
if ($status) $ordersQuery->where('status', $status);
}
// 3. 執行分頁 (使用獨立的 pageName)
$perPage = $request->input('per_page', 10);
$data['orders'] = $ordersQuery->latest()->paginate($perPage, ['*'], 'orders_page')->withQueryString();
$data['invoices'] = $invoicesQuery->latest()->paginate($perPage, ['*'], 'invoices_page')->withQueryString();
$data['dispenseLogs'] = $dispenseQuery->latest()->paginate($perPage, ['*'], 'dispense_page')->withQueryString();
if ($isAjax) {
return response()->json([
'success' => true,
'tab' => $tab,
'html' => view('admin.sales.partials.tab-' . $tab, $data)->render()
]);
}
return view('admin.sales.index', $data);
}
/**
* 取得單筆交易詳情 (用於 Slide-over)
*/
public function show(Order $order)
{
$order->load(['machine', 'invoice', 'items', 'dispenseRecords.product']);
return response()->json([
'success' => true,
'html' => view('admin.sales.partials.order-detail-panel', [
'order' => $order,
'paymentTypes' => [
1 => '信用卡', 2 => '電子票證', 3 => '掃碼支付', 4 => '紙鈔機', 9 => '零錢',
30 => 'LINE Pay', 31 => '街口支付', 32 => '悠遊付', 33 => 'Pi 拍錢包', 34 => '全盈+PAY',
60 => '點數/優惠券',
]
])->render()
]);
}
@ -59,7 +183,7 @@ class SalesController extends Controller
$query->where('status', trim($request->status));
}
$per_page = $request->input('per_page', 15);
$per_page = $request->input('per_page', 10);
$data['pickupCodes'] = $query->paginate($per_page, ['*'], 'list_page')->withQueryString();
// 供新增彈窗使用的機台清單
@ -95,7 +219,7 @@ class SalesController extends Controller
}
$data['logs'] = $logQuery->latest()
->paginate($request->input('per_page', 15), ['*'], 'log_page')
->paginate($request->input('per_page', 10), ['*'], 'log_page')
->withQueryString();
// 定義可用動作
@ -272,7 +396,7 @@ class SalesController extends Controller
}
}
$data['passCodes'] = $query->paginate($request->input('per_page', 15), ['*'], 'list_page')->withQueryString();
$data['passCodes'] = $query->paginate($request->input('per_page', 10), ['*'], 'list_page')->withQueryString();
$data['machines'] = Machine::all();
}
@ -305,7 +429,7 @@ class SalesController extends Controller
}
$data['logs'] = $logQuery->latest()
->paginate($request->input('per_page', 15), ['*'], 'log_page')
->paginate($request->input('per_page', 10), ['*'], 'log_page')
->withQueryString();
// 定義可用動作

View File

@ -29,7 +29,7 @@ class ProcessTransaction implements ShouldQueue
/**
* Execute the job.
*/
public function handle(\App\Services\Transaction\TransactionService $transactionService): void
public function handle(\App\Services\Transaction\TransactionService $transactionService, \App\Services\Machine\MqttService $mqttService): void
{
$machine = Machine::withoutGlobalScopes()->where('serial_no', $this->serialNo)->first();
@ -38,6 +38,8 @@ class ProcessTransaction implements ShouldQueue
return;
}
$flowId = $this->payload['flow_id'] ?? 'N/A';
try {
// 將 serial_no 放入 payload 供 Service 使用
$data = $this->payload;
@ -50,11 +52,30 @@ class ProcessTransaction implements ShouldQueue
$machine->company_id,
"Transaction processed: :id",
'info',
array_merge($this->payload, ['id' => ($this->payload['flow_id'] ?? 'N/A')]),
array_merge($this->payload, ['id' => $flowId]),
'transaction'
);
// 發送成功回饋 (ACK)
$mqttService->pushCommand($this->serialNo, 'transaction_ack', [
'flow_id' => $flowId,
'status' => 'success',
'message' => 'Processed successfully'
], (string) $flowId);
} catch (\Exception $e) {
Log::error("Failed to process MQTT transaction for machine {$this->serialNo}: " . $e->getMessage());
Log::error("Failed to process MQTT transaction for machine {$this->serialNo}: " . $e->getMessage(), [
'payload' => $this->payload,
'exception' => $e
]);
// 發送失敗回饋 (ACK)
$mqttService->pushCommand($this->serialNo, 'transaction_ack', [
'flow_id' => $flowId,
'status' => 'error',
'message' => $e->getMessage()
], (string) $flowId);
throw $e;
}
}

View File

@ -27,12 +27,34 @@ class ProcessDispenseRecord implements ShouldQueue
/**
* Execute the job.
*/
public function handle(TransactionService $transactionService): void
public function handle(\App\Services\Transaction\TransactionService $transactionService, \App\Services\Machine\MqttService $mqttService): void
{
$serialNo = $this->data['serial_no'] ?? 'unknown';
$orderNo = $this->data['order_no'] ?? 'N/A';
try {
$transactionService->recordDispense($this->data);
// 發送成功回饋 (ACK)
$mqttService->pushCommand($serialNo, 'dispense_ack', [
'order_no' => $orderNo,
'status' => 'success',
'message' => 'Processed successfully'
], $orderNo);
} catch (\Exception $e) {
Log::error("Failed to record dispense for machine {$this->data['serial_no']}: " . $e->getMessage());
Log::error("Failed to record dispense for machine {$serialNo}: " . $e->getMessage(), [
'data' => $this->data,
'exception' => $e
]);
// 發送失敗回饋 (ACK)
$mqttService->pushCommand($serialNo, 'dispense_ack', [
'order_no' => $orderNo,
'status' => 'error',
'message' => $e->getMessage()
], $orderNo);
throw $e;
}
}

View File

@ -27,15 +27,34 @@ class ProcessInvoice implements ShouldQueue
/**
* Execute the job.
*/
public function handle(TransactionService $transactionService): void
public function handle(\App\Services\Transaction\TransactionService $transactionService, \App\Services\Machine\MqttService $mqttService): void
{
$serialNo = $this->data['serial_no'] ?? 'unknown';
$invoiceNo = $this->data['invoice_no'] ?? 'N/A';
try {
$transactionService->recordInvoice($this->data);
// 發送成功回饋 (ACK)
$mqttService->pushCommand($serialNo, 'invoice_ack', [
'invoice_no' => $invoiceNo,
'status' => 'success',
'message' => 'Processed successfully'
], $invoiceNo);
} catch (\Exception $e) {
Log::error('Failed to process invoice: ' . $e->getMessage(), [
'data' => $this->data,
'exception' => $e
]);
// 發送失敗回饋 (ACK)
$mqttService->pushCommand($serialNo, 'invoice_ack', [
'invoice_no' => $invoiceNo,
'status' => 'error',
'message' => $e->getMessage()
], $invoiceNo);
throw $e;
}
}

View File

@ -31,7 +31,7 @@ class ProcessTransactionFinalized implements ShouldQueue
/**
* Execute the job.
*/
public function handle(TransactionService $transactionService): void
public function handle(\App\Services\Transaction\TransactionService $transactionService, \App\Services\Machine\MqttService $mqttService): void
{
$machine = Machine::withoutGlobalScopes()->where('serial_no', $this->serialNo)->first();
@ -40,6 +40,8 @@ class ProcessTransactionFinalized implements ShouldQueue
return;
}
$flowId = $this->payload['order']['flow_id'] ?? ($this->payload['flow_id'] ?? 'N/A');
try {
$data = $this->payload;
$data['serial_no'] = $this->serialNo;
@ -63,11 +65,27 @@ class ProcessTransactionFinalized implements ShouldQueue
'order_no' => $order->order_no
]);
// 發送成功回饋 (ACK)
$mqttService->pushCommand($this->serialNo, 'transaction_ack', [
'flow_id' => $flowId,
'order_no' => $order->order_no,
'status' => 'success',
'message' => 'Processed successfully'
], (string) $flowId);
} catch (\Exception $e) {
Log::error("Failed to process MQTT transaction finalized for machine {$this->serialNo}: " . $e->getMessage(), [
'payload' => $this->payload,
'exception' => $e
]);
// 發送失敗回饋 (ACK)
$mqttService->pushCommand($this->serialNo, 'transaction_ack', [
'flow_id' => $flowId,
'status' => 'error',
'message' => $e->getMessage()
], (string) $flowId);
throw $e;
}
}

View File

@ -19,6 +19,7 @@ class Invoice extends Model
'amount',
'carrier_id',
'invoice_date',
'machine_time',
'random_number',
'love_code',
'rtn_code',
@ -29,6 +30,7 @@ class Invoice extends Model
protected $casts = [
'total_amount' => 'decimal:2',
'tax_amount' => 'decimal:2',
'machine_time' => 'datetime',
'metadata' => 'array',
];
@ -36,4 +38,9 @@ class Invoice extends Model
{
return $this->belongsTo(Order::class);
}
public function machine()
{
return $this->belongsTo(\App\Models\Machine\Machine::class);
}
}

View File

@ -8,6 +8,7 @@ use App\Models\Transaction\Invoice;
use App\Models\Transaction\DispenseRecord;
use Illuminate\Support\Facades\DB;
use App\Models\Machine\Machine;
use Illuminate\Support\Facades\Log;
class TransactionService
{
@ -16,6 +17,17 @@ class TransactionService
*/
public function processTransaction(array $data): Order
{
$flowId = $data['flow_id'] ?? null;
if ($flowId) {
$existingOrder = Order::withoutGlobalScopes()
->where('flow_id', $flowId)
->first();
if ($existingOrder) {
return $existingOrder;
}
}
return DB::transaction(function () use ($data) {
$machine = Machine::where('serial_no', $data['serial_no'])->firstOrFail();
@ -105,6 +117,7 @@ class TransactionService
'amount' => $data['amount'] ?? 0,
'carrier_id' => $data['carrier_id'] ?? null,
'invoice_date' => $data['invoice_date'] ?? null,
'machine_time' => $data['machine_time'] ?? null,
'random_number' => $data['random_number'] ?? ($data['random_no'] ?? null),
'love_code' => $data['love_code'] ?? null,
'rtn_code' => $data['rtn_code'] ?? null,
@ -137,7 +150,7 @@ class TransactionService
'amount' => $data['amount'] ?? 0,
'remaining_stock' => $data['remaining_stock'] ?? null,
'dispense_status' => $data['dispense_status'] ?? 0,
'member_barcode' => $data['member_barcode'] ?? null,
'member_barcode' => $data['member_barcode'] ?? ($order?->member_barcode ?? null),
'machine_time' => $data['machine_time'] ?? now(),
'points_used' => $data['points_used'] ?? 0,
]);
@ -149,6 +162,19 @@ class TransactionService
*/
public function finalizeTransaction(array $data): Order
{
$flowId = $data['order']['flow_id'] ?? null;
// 冪等性檢查:如果 flow_id 已經存在,直接回傳既有訂單
if ($flowId) {
$existingOrder = Order::withoutGlobalScopes()
->where('flow_id', $flowId)
->first();
if ($existingOrder) {
Log::info("Transaction already finalized, returning existing order", ['flow_id' => $flowId]);
return $existingOrder;
}
}
return DB::transaction(function () use ($data) {
$serialNo = $data['serial_no'];
@ -173,6 +199,12 @@ class TransactionService
$dispenseItem['serial_no'] = $serialNo;
$dispenseItem['flow_id'] = $order->flow_id;
$dispenseItem['order_id'] = $order->id;
// 自動繼承:如果出貨紀錄沒帶 Barcode就用訂單的
if (empty($dispenseItem['member_barcode']) && !empty($order->member_barcode)) {
$dispenseItem['member_barcode'] = $order->member_barcode;
}
$this->recordDispense($dispenseItem);
}
}

View File

@ -525,6 +525,7 @@ return [
'invoice' => ['type' => 'object', 'description' => '(選填) 發票開立資訊 (若無發票可省略)', 'required' => false],
'invoice.invoice_no' => ['type' => 'string', 'description' => '(必填若有invoice) 發票號碼', 'required' => false],
'invoice.invoice_date' => ['type' => 'string', 'description' => '(必填若有invoice) 發票日期 (Y-m-d)', 'required' => false],
'invoice.machine_time' => ['type' => 'datetime', 'description' => '(必填若有invoice) 機台開立發票時間 (Y-m-d H:i:s)', 'required' => false],
'invoice.amount' => ['type' => 'numeric', 'description' => '(必填若有invoice) 發票金額', 'required' => false],
'invoice.random_number' => ['type' => 'string', 'description' => '(必填若有invoice) 發票隨機碼 (4位)', 'required' => false],
'invoice.love_code' => ['type' => 'string', 'description' => '(選填) 捐贈碼', 'required' => false],

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('invoices', function (Blueprint $table) {
$table->datetime('machine_time')->nullable()->after('invoice_date')->comment('機台開立發票時間');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('invoices', function (Blueprint $table) {
$table->dropColumn('machine_time');
});
}
};

View File

@ -74,6 +74,7 @@
"Affiliated Company": "Affiliated Company",
"Affiliated Unit": "Company Name",
"Affiliation": "Company Name",
"After Qty": "After Qty",
"Alert Summary": "Alert Summary",
"Alerts": "Alerts",
"Alerts Pending": "Alerts Pending",
@ -94,6 +95,7 @@
"All Warehouses": "All Warehouses",
"All slots are fully stocked": "All slots are fully stocked",
"Amount": "Amount",
"Amount / Payment": "Amount / Payment",
"An error occurred while saving.": "An error occurred while saving.",
"Analysis Management": "Analysis Management",
"Analysis Permissions": "Analysis Permissions",
@ -146,6 +148,7 @@
"Assigned Machines": "Assigned Machines",
"Assigned To": "Assigned To",
"Assignment removed successfully.": "Assignment removed successfully.",
"Associated Order": "Associated Order",
"Audit Management": "Audit Management",
"Audit Permissions": "Audit Permissions",
"Authorization updated successfully": "Authorization updated successfully",
@ -173,6 +176,7 @@
"Batch": "Batch",
"Batch No": "Batch No",
"Batch Number": "Batch Number",
"Before Qty": "Before Qty",
"Belongs To": "Company Name",
"Belongs To Company": "Company Name",
"Branch": "Branch",
@ -372,6 +376,7 @@
"Delivery door closed": "Delivery door closed",
"Delivery door open error": "Delivery door open error",
"Delivery door opened": "Delivery door opened",
"Delta": "Delta",
"Deposit Bonus": "Deposit Bonus",
"Describe the repair or maintenance status...": "Describe the repair or maintenance status...",
"Description / Name": "Description / Name",
@ -390,8 +395,14 @@
"Disabled": "Disabled",
"Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?",
"Discord Notifications": "Discord Notifications",
"Discount": "Discount",
"Dispense Failed": "Dispense Failed",
"Dispense Pending": "Dispense Pending",
"Dispense Product": "Dispense Product",
"Dispense Quantity": "Dispense Quantity",
"Dispense Status": "Dispense Status",
"Dispense Success": "Dispense Success",
"Dispense Time": "Dispense Time",
"Dispense error (0407)": "Dispense error (0407)",
"Dispense error (0408)": "Dispense error (0408)",
"Dispense error (0409)": "Dispense error (0409)",
@ -429,6 +440,7 @@
"Edit Machine Name": "Edit Machine Name",
"Edit Machine Settings": "Edit Machine Settings",
"Edit Name": "Edit Name",
"Edit Pass Code": "Edit Pass Code",
"Edit Payment Config": "Edit Payment Config",
"Edit Product": "Edit Product",
"Edit Role": "Edit Role",
@ -517,6 +529,8 @@
"Firmware updated to :version": "Firmware updated to :version",
"Fleet Avg OEE": "Fleet Avg OEE",
"Fleet Performance": "Fleet Performance",
"Flow ID": "Flow ID",
"Flow ID / Time": "Flow ID / Time",
"Force End Session": "Force End Session",
"Force end current session": "Force end current session",
"From": "From",
@ -536,6 +550,7 @@
"Gift Definitions": "Gift Definitions",
"Global roles accessible by all administrators.": "Global roles accessible by all administrators.",
"Go Back": "Go Back",
"Go to E-Invoice": "Go to E-Invoice",
"Goods & System Settings": "Goods & System Settings",
"Got it": "Got it",
"Grant UI Access": "Grant UI Access",
@ -580,7 +595,12 @@
"Inventory Overview": "Inventory Overview",
"Inventory Stable": "Inventory Stable",
"Inventory synced with machine": "Inventory synced with machine",
"Invoice Date": "Invoice Date",
"Invoice Information": "Invoice Information",
"Invoice Number": "Invoice Number",
"Invoice Number / Flow ID": "Invoice Number / Flow ID",
"Invoice Status": "Invoice Status",
"Issued At": "Issued At",
"Item List": "Item List",
"Items": "Items",
"JKO_MERCHANT_ID": "JKO_MERCHANT_ID",
@ -621,8 +641,8 @@
"Live Fleet Updates": "Live Fleet Updates",
"Loading Cabinet...": "Loading Cabinet...",
"Loading Data": "Loading Data",
"Loading machines...": "Loading machines...",
"Loading failed": "Loading failed",
"Loading machines...": "Loading machines...",
"Loading...": "Loading...",
"Location": "Location",
"Location found!": "Location found!",
@ -645,6 +665,7 @@
"Low stock and out-of-stock alerts": "Low stock and out-of-stock alerts",
"Loyalty & Features": "Loyalty & Features",
"Machine": "Machine",
"Machine / Date": "Machine / Date",
"Machine / Slot": "Machine / Slot",
"Machine Advertisement Settings": "Machine Advertisement Settings",
"Machine Count": "Machine Count",
@ -671,11 +692,13 @@
"Machine Reports": "Machine Reports",
"Machine Restart": "Machine Restart",
"Machine Return": "Machine Return",
"Machine Serial": "Machine Serial",
"Machine Serial No": "Machine Serial No",
"Machine Settings": "Machine Settings",
"Machine Status": "Machine Status",
"Machine Status List": "Machine Status List",
"Machine Stock": "Machine Stock",
"Machine Stock Movements": "Machine Stock Movements",
"Machine System Settings": "Machine System Settings",
"Machine Utilization": "Machine Utilization",
"Machine created successfully.": "Machine created successfully.",
@ -771,17 +794,6 @@
"Motor not stopped": "Motor not stopped",
"Movement History": "Movement History",
"Movement Logs": "Movement Logs",
"Machine Stock Movements": "Machine Stock Movements",
"Stock Movement Log": "Stock Movement Log",
"movement.type.replenishment": "Replenishment",
"movement.type.pickup": "Pickup Code Consumed",
"movement.type.remote_dispense": "Remote Dispense",
"movement.type.adjustment": "Stock Adjustment",
"movement.type.rollback": "Dispense Rollback",
"Before Qty": "Before Qty",
"After Qty": "After Qty",
"Delta": "Delta",
"No movements found": "No movements found",
"Multilingual Names": "Multilingual Names",
"N/A": "N/A",
"Name": "Name",
@ -814,6 +826,7 @@
"No advertisements found.": "No advertisements found.",
"No alert summary": "No alert summary",
"No assignments": "No assignments",
"No associated order found": "No associated order found",
"No categories found.": "No categories found.",
"No command history": "No command history",
"No configurations found": "No configurations found",
@ -836,12 +849,15 @@
"No matching machines": "No matching machines",
"No materials available": "No materials available",
"No movement records found": "No movement records found",
"No movements found": "No movements found",
"No pass codes found": "No pass codes found",
"No permissions": "No permissions",
"No pickup codes found": "No pickup codes found",
"No product record found": "No product record found",
"No products found in this warehouse": "No products found in this warehouse",
"No products found matching your criteria.": "No products found matching your criteria.",
"No records found": "No records found",
"No related detail found": "No related detail found",
"No replenishment orders found": "No replenishment orders found",
"No results found": "No results found",
"No roles available": "No roles available",
@ -852,6 +868,7 @@
"No slots need replenishment": "No slots need replenishment",
"No stock data found": "No stock data found",
"No stock-in orders found": "No stock-in orders found",
"No transaction orders found": "No transaction orders found",
"No transfer orders found": "No transfer orders found",
"No users found": "No users found",
"No warehouses found": "No warehouses found",
@ -895,8 +912,12 @@
"Optional remarks...": "Optional remarks...",
"Order Details": "Order Details",
"Order Info": "Order Info",
"Order Items": "Order Items",
"Order Management": "Order Management",
"Order No.": "Order No.",
"Order Number / Time": "Order Number / Time",
"Order Status": "Order Status",
"Order Time": "Order Time",
"Order already completed": "Order already completed",
"Order already processed": "Order already processed",
"Order completed and stock updated": "Order completed and stock updated",
@ -958,6 +979,7 @@
"Password": "Password",
"Password updated successfully.": "密碼已成功變更。",
"Payment & Invoice": "Payment & Invoice",
"Payment Amount": "Payment Amount",
"Payment Buffer Seconds": "Payment Buffer Seconds",
"Payment Config": "Payment Config",
"Payment Configuration": "Payment Configuration",
@ -965,11 +987,13 @@
"Payment Configuration deleted successfully.": "Payment Configuration deleted successfully.",
"Payment Configuration updated successfully.": "Payment Configuration updated successfully.",
"Payment Selection": "Payment Selection",
"Payment Type": "Payment Type",
"Pending": "Pending",
"Per Page": "Per Page",
"Performance": "Performance",
"Permanent": "Permanent",
"Permanent Code": "Permanent Code",
"Permanent if empty": "Permanent if empty",
"Permanently Delete Account": "Permanently Delete Account",
"Permission Settings": "Permission Settings",
"Permission Tags": "Permission Tags",
@ -1019,6 +1043,7 @@
"Previous": "Previous",
"Price / Member": "Price / Member",
"Pricing Information": "Pricing Information",
"Print Invoice": "Print Invoice",
"Product": "Product",
"Product / Slot": "Product / Slot",
"Product / Stock": "Product / Stock",
@ -1083,6 +1108,7 @@
"Recent Login": "Recent Login",
"Recently reported errors or warnings in logs": "Recently reported errors or warnings in logs",
"Records": "Records",
"Refunded": "Refunded",
"Regenerate": "Regenerate",
"Regenerating the token will disconnect the physical machine until it is updated. Continue?": "Regenerating the token will disconnect the physical machine until it is updated. Continue?",
"Remote Change": "Remote Change",
@ -1172,9 +1198,10 @@
"Scan to pick up your product": "Scan to pick up your product",
"Schedule": "Schedule",
"Search Company Title...": "Search Company Title...",
"Search Flow ID / Slot...": "Search Flow ID / Slot...",
"Search Invoice No / Flow ID...": "Search Invoice No / Flow ID...",
"Search Machine...": "Search Machine...",
"Search logs...": "Search logs...",
"Searchable Select": "Searchable Select",
"Search Order No / Flow ID / Invoice...": "Search Order No / Flow ID / Invoice...",
"Search accounts...": "Search accounts...",
"Search by code, machine name or serial...": "Search by code, machine name or serial...",
"Search by code, name or machine...": "Search by code, name or machine...",
@ -1185,6 +1212,7 @@
"Search company...": "Search company...",
"Search configurations...": "Search configurations...",
"Search customers...": "Search customers...",
"Search logs...": "Search logs...",
"Search machines by name or serial...": "Search machines by name or serial...",
"Search machines...": "Search machines...",
"Search models...": "Search models...",
@ -1197,6 +1225,7 @@
"Search users...": "Search users...",
"Search warehouses...": "Search warehouses...",
"Search...": "Search...",
"Searchable Select": "Searchable Select",
"Searching...": "Searching...",
"Seconds": "Seconds",
"Security & State": "Security & State",
@ -1253,6 +1282,7 @@
"Sign in to your account": "Sign in to your account",
"Signed in as": "Signed in as",
"Slot": "Slot",
"Slot / Product": "Slot / Product",
"Slot Mechanism (default: Conveyor, check for Spring)": "Slot Mechanism (default: Conveyor, check for Spring)",
"Slot No": "Slot No",
"Slot Status": "Slot Status",
@ -1293,6 +1323,7 @@
"Start Time": "Start Time",
"Statistics": "Statistics",
"Status": "Status",
"Status / Invoice": "Status / Invoice",
"Status / Temp / Sub / Card / Scan": "Status / Temp / Sub / Card / Scan",
"Status Timeline": "Status Timeline",
"Status updated successfully": "Status updated successfully",
@ -1306,6 +1337,7 @@
"Stock Level > 50%": "Stock Level > 50%",
"Stock Levels": "Stock Levels",
"Stock Management": "Stock Management",
"Stock Movement Log": "Stock Movement Log",
"Stock Out": "Stock Out",
"Stock Quantity": "Stock Quantity",
"Stock Rate": "Stock Rate",
@ -1316,6 +1348,7 @@
"Stock returned to warehouse": "Stock returned to warehouse",
"Stock-In Management": "Stock-In Management",
"Stock-In Order": "Stock-In Order",
"Stock-In Order Details": "Stock-In Order Details",
"Stock-In Orders": "Stock-In Orders",
"Stock-In Orders Tab": "Stock-In Orders",
"Stock-in order": "Stock-in order",
@ -1335,6 +1368,7 @@
"Sub-actions": "子項目",
"Sub-machine Status Request": "Sub-machine Status",
"Submit Record": "Submit Record",
"Subtotal": "Subtotal",
"Success": "Success",
"Super Admin": "Super Admin",
"Super-admin role cannot be assigned to tenant accounts.": "Super-admin role cannot be assigned to tenant accounts.",
@ -1419,6 +1453,8 @@
"Track stock levels, stock-in orders, and movement history": "Track stock levels, stock-in orders, and movement history",
"Track stock-in orders and movement history": "Track stock-in orders and movement history",
"Traditional Chinese": "Traditional Chinese",
"Transaction Info": "Transaction Info",
"Transaction Time": "Transaction Time",
"Transaction processed: :id": "Transaction processed: :id",
"Transfer Audit": "Transfer Audit",
"Transfer Details": "Transfer Details",
@ -1460,15 +1496,10 @@
"Unlock Now": "Unlock Now",
"Unlock Page": "Unlock Page",
"Unpublish": "Unpublish",
"cancel": "Cancel",
"create": "Create",
"update": "Update",
"Update": "Update",
"Edit Pass Code": "Edit Pass Code",
"Permanent if empty": "Permanent if empty",
"Update Pass Code": "Update Pass Code",
"Update Authorization": "Update Authorization",
"Update Customer": "Update Customer",
"Update Pass Code": "Update Pass Code",
"Update Password": "Update Password",
"Update Product": "Update Product",
"Update Settings": "Update Settings",
@ -1505,12 +1536,14 @@
"View Inventory": "View Inventory",
"View Logs": "View Logs",
"View More": "View More",
"View Order": "View Order",
"View QR Code": "View QR Code",
"View Slots": "View Slots",
"View all warehouses": "View all warehouses",
"View slot-level inventory for each machine": "View slot-level inventory for each machine",
"Visit Gift": "Visit Gift",
"Visual overview of all machine locations": "Visual overview of all machine locations",
"Void Invoice": "Void Invoice",
"Waiting": "Waiting",
"Waiting for Payment": "Waiting for Payment",
"Warehouse": "Warehouse",
@ -1563,13 +1596,18 @@
"accounts": "Account Management",
"admin": "管理員",
"analysis": "Analysis Management",
"and :count other items": "and :count other items",
"app": "APP Management",
"audit": "Audit Management",
"basic-settings": "Basic Settings",
"basic.machines": "機台設定",
"basic.payment-configs": "客戶金流設定",
"by": "by",
"cancel": "Cancel",
"companies": "Customer Management",
"consume": "Pickup Success",
"consume_failed": "Pickup Failed",
"create": "Create",
"data-config": "Data Configuration",
"data-config.sub-account-roles": "子帳號角色",
"data-config.sub-accounts": "子帳號管理",
@ -1645,6 +1683,11 @@
"menu.warehouses.transfers": "menu.warehouses.transfers",
"min": "min",
"mins ago": "mins ago",
"movement.type.adjustment": "Stock Adjustment",
"movement.type.pickup": "Pickup Code Consumed",
"movement.type.remote_dispense": "Remote Dispense",
"movement.type.replenishment": "Replenishment",
"movement.type.rollback": "Dispense Rollback",
"of": "of",
"of items": "of items",
"pending": "pending",
@ -1664,15 +1707,14 @@
"success": "success",
"super-admin": "super-admin",
"to": "to",
"update": "Update",
"used": "Used",
"user": "user",
"vending": "vending",
"verify_success": "Verification Success",
"video": "video",
"visit_gift": "visit_gift",
"vs Yesterday": "vs Yesterday",
"warehouses": "warehouses",
"used": "Used",
"待填寫": "待填寫",
"verify_success": "Verification Success",
"consume": "Pickup Success",
"consume_failed": "Pickup Failed"
}
"待填寫": "待填寫"
}

View File

@ -74,6 +74,7 @@
"Affiliated Company": "Affiliated Company",
"Affiliated Unit": "Affiliated Unit",
"Affiliation": "Affiliation",
"After Qty": "変動後数量",
"Alert Summary": "Alert Summary",
"Alerts": "Alerts",
"Alerts Pending": "Alerts Pending",
@ -94,6 +95,7 @@
"All Warehouses": "すべての倉庫",
"All slots are fully stocked": "全スロットが満在庫です",
"Amount": "Amount",
"Amount / Payment": "金額 / 支払い",
"An error occurred while saving.": "An error occurred while saving.",
"Analysis Management": "Analysis Management",
"Analysis Permissions": "Analysis Permissions",
@ -146,6 +148,7 @@
"Assigned Machines": "Assigned Machines",
"Assigned To": "担当者",
"Assignment removed successfully.": "Assignment removed successfully.",
"Associated Order": "関連注文",
"Audit Management": "Audit Management",
"Audit Permissions": "Audit Permissions",
"Authorization updated successfully": "Authorization updated successfully",
@ -173,6 +176,7 @@
"Batch": "Batch",
"Batch No": "Batch No",
"Batch Number": "Batch Number",
"Before Qty": "変動前数量",
"Belongs To": "Belongs To",
"Belongs To Company": "Belongs To Company",
"Branch": "分倉庫",
@ -259,9 +263,9 @@
"Confirm Account Status Change": "Confirm Account Status Change",
"Confirm Assignment": "Confirm Assignment",
"Confirm Cancel": "キャンセル確認",
"Confirm Complete": "完了を確認",
"Confirm Deactivation": "無効化の確認",
"Confirm Delete": "削除の確認",
"Confirm Complete": "完了を確認",
"Confirm Deletion": "削除の確認",
"Confirm Password": "Confirm Password",
"Confirm Prepare": "準備確認",
@ -356,8 +360,6 @@
"Default Not Donate": "Default Not Donate",
"Define and manage security roles and permissions.": "Define and manage security roles and permissions.",
"Define new third-party payment parameters": "Define new third-party payment parameters",
"Yes, Deactivate": "はい、無効にします",
"Yes, Delete": "はい、削除します",
"Delete Account": "Delete Account",
"Delete Advertisement": "Delete Advertisement",
"Delete Advertisement Confirmation": "Delete Advertisement Confirmation",
@ -373,6 +375,7 @@
"Delivery door closed": "Delivery door closed",
"Delivery door open error": "Delivery door open error",
"Delivery door opened": "Delivery door opened",
"Delta": "変動量",
"Deposit Bonus": "Deposit Bonus",
"Describe the repair or maintenance status...": "Describe the repair or maintenance status...",
"Description / Name": "説明 / 名称",
@ -391,8 +394,14 @@
"Disabled": "無効",
"Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "この顧客を無効化すると、この顧客のすべてのアカウントも無効になります。続けますか?",
"Discord Notifications": "Discord Notifications",
"Dispense Failed": "Dispense Failed",
"Dispense Success": "Dispense Success",
"Discount": "割引",
"Dispense Failed": "配送失敗",
"Dispense Pending": "配送保留",
"Dispense Product": "配送商品",
"Dispense Quantity": "配送数量",
"Dispense Status": "配送ステータス",
"Dispense Success": "配送成功",
"Dispense Time": "配送時間",
"Dispense error (0407)": "Dispense error (0407)",
"Dispense error (0408)": "Dispense error (0408)",
"Dispense error (0409)": "Dispense error (0409)",
@ -430,6 +439,7 @@
"Edit Machine Name": "Edit Machine Name",
"Edit Machine Settings": "Edit Machine Settings",
"Edit Name": "Edit Name",
"Edit Pass Code": "通行コード編集",
"Edit Payment Config": "Edit Payment Config",
"Edit Product": "Edit Product",
"Edit Role": "Edit Role",
@ -499,7 +509,7 @@
"Expiry Management": "Expiry Management",
"Expiry Time": "有効期限時間",
"Expiry date tracking and warnings": "Expiry date tracking and warnings",
"Failed": "Failed",
"Failed": "失敗",
"Failed to fetch machine data.": "Failed to fetch machine data.",
"Failed to load permissions": "Failed to load permissions",
"Failed to load preview": "プレビューの読み込みに失敗しました",
@ -518,6 +528,8 @@
"Firmware updated to :version": "Firmware updated to :version",
"Fleet Avg OEE": "Fleet Avg OEE",
"Fleet Performance": "Fleet Performance",
"Flow ID": "フローID",
"Flow ID / Time": "流水番号 / 時間",
"Force End Session": "Force End Session",
"Force end current session": "Force end current session",
"From": "発送元",
@ -537,6 +549,7 @@
"Gift Definitions": "Gift Definitions",
"Global roles accessible by all administrators.": "Global roles accessible by all administrators.",
"Go Back": "戻る",
"Go to E-Invoice": "電子請求書へ",
"Goods & System Settings": "商品とシステム設定",
"Got it": "Got it",
"Grant UI Access": "Grant UI Access",
@ -581,7 +594,12 @@
"Inventory Overview": "在庫概要",
"Inventory Stable": "Inventory Stable",
"Inventory synced with machine": "Inventory synced with machine",
"Invoice Date": "請求書日付",
"Invoice Information": "請求情報",
"Invoice Number": "請求書番号",
"Invoice Number / Flow ID": "請求書番号 / 流水番号",
"Invoice Status": "Invoice Status",
"Issued At": "発行時間",
"Item List": "品目リスト",
"Items": "Items",
"JKO_MERCHANT_ID": "JKO_MERCHANT_ID",
@ -622,8 +640,8 @@
"Live Fleet Updates": "Live Fleet Updates",
"Loading Cabinet...": "Loading Cabinet...",
"Loading Data": "Loading Data",
"Loading machines...": "マシンの読み込み中...",
"Loading failed": "読み込みに失敗しました",
"Loading machines...": "マシンの読み込み中...",
"Loading...": "読み込み中...",
"Location": "Location",
"Location found!": "Location found!",
@ -646,7 +664,8 @@
"Low stock and out-of-stock alerts": "Low stock and out-of-stock alerts",
"Loyalty & Features": "Loyalty & Features",
"Machine": "マシン",
"Machine / Slot": "機台 / 貨道",
"Machine / Date": "機台 / 日付",
"Machine / Slot": "機台 / スロット",
"Machine Advertisement Settings": "Machine Advertisement Settings",
"Machine Count": "Machine Count",
"Machine Details": "Machine Details",
@ -664,7 +683,7 @@
"Machine Management Permissions": "Machine Management Permissions",
"Machine Model": "Machine Model",
"Machine Model Settings": "Machine Model Settings",
"Machine Name": "Machine Name",
"Machine Name": "機台名",
"Machine Permissions": "Machine Permissions",
"Machine Reboot": "Machine Reboot",
"Machine Registry": "Machine Registry",
@ -672,11 +691,13 @@
"Machine Reports": "Machine Reports",
"Machine Restart": "Machine Restart",
"Machine Return": "機台返品",
"Machine Serial": "機台シリアル",
"Machine Serial No": "Machine Serial No",
"Machine Settings": "Machine Settings",
"Machine Status": "Machine Status",
"Machine Status List": "Machine Status List",
"Machine Stock": "マシン在庫",
"Machine Stock Movements": "機台在庫異動履歴",
"Machine System Settings": "機台システム設定",
"Machine Utilization": "Machine Utilization",
"Machine created successfully.": "Machine created successfully.",
@ -769,17 +790,6 @@
"Motor not stopped": "Motor not stopped",
"Movement History": "移動履歴",
"Movement Logs": "移動ログ",
"Machine Stock Movements": "機台在庫異動履歴",
"Stock Movement Log": "在庫異動ログ",
"movement.type.replenishment": "補充入庫",
"movement.type.pickup": "受取コード消費",
"movement.type.remote_dispense": "リモート出庫",
"movement.type.adjustment": "棚卸調整",
"movement.type.rollback": "出庫失敗ロールバック",
"Before Qty": "変動前数量",
"After Qty": "変動後数量",
"Delta": "変動量",
"No movements found": "異動記録なし",
"Multilingual Names": "Multilingual Names",
"N/A": "N/A",
"Name": "Name",
@ -812,6 +822,7 @@
"No advertisements found.": "No advertisements found.",
"No alert summary": "No alert summary",
"No assignments": "No assignments",
"No associated order found": "関連注文が見つかりません",
"No categories found.": "No categories found.",
"No command history": "No command history",
"No configurations found": "No configurations found",
@ -834,12 +845,15 @@
"No matching machines": "No matching machines",
"No materials available": "No materials available",
"No movement records found": "移動履歴が見つかりません",
"No movements found": "異動記録なし",
"No pass codes found": "通行コードが見つかりません",
"No permissions": "No permissions",
"No pickup codes found": "受取コードが見つかりません",
"No product record found": "商品記録が見つかりません",
"No products found in this warehouse": "この倉庫に在庫はありません",
"No products found matching your criteria.": "No products found matching your criteria.",
"No records found": "No records found",
"No related detail found": "関連詳細が見つかりません",
"No replenishment orders found": "補充伝票が見つかりません",
"No results found": "No results found",
"No roles available": "No roles available",
@ -850,6 +864,7 @@
"No slots need replenishment": "補充が必要なスロットはありません",
"No stock data found": "在庫データが見つかりません",
"No stock-in orders found": "入庫伝票が見つかりません",
"No transaction orders found": "取引注文が見つかりません",
"No transfer orders found": "転送伝票が見つかりません",
"No users found": "No users found",
"No warehouses found": "倉庫が見つかりません",
@ -891,10 +906,14 @@
"Optional": "任意",
"Optional remarks": "任意備考",
"Optional remarks...": "任意の備考...",
"Order Details": "入庫伝票詳細",
"Order Details": "注文詳細",
"Order Info": "注文情報",
"Order Items": "注文商品",
"Order Management": "Order Management",
"Order No.": "伝票番号",
"Order Number / Time": "注文番号 / 時間",
"Order Status": "注文ステータス",
"Order Time": "注文時間",
"Order already completed": "注文はすでに完了しています",
"Order already processed": "注文はすでに処理されています",
"Order completed and stock updated": "注文が完了し、在庫が更新されました",
@ -955,7 +974,8 @@
"Pass code updated.": "通行コードが更新されました。",
"Password": "Password",
"Password updated successfully.": "Password updated successfully.",
"Payment & Invoice": "Payment & Invoice",
"Payment & Invoice": "決済と請求書",
"Payment Amount": "支払い金額",
"Payment Buffer Seconds": "Payment Buffer Seconds",
"Payment Config": "Payment Config",
"Payment Configuration": "Payment Configuration",
@ -963,11 +983,13 @@
"Payment Configuration deleted successfully.": "Payment Configuration deleted successfully.",
"Payment Configuration updated successfully.": "Payment Configuration updated successfully.",
"Payment Selection": "Payment Selection",
"Payment Type": "決済方法",
"Pending": "保留中",
"Per Page": "Per Page",
"Performance": "Performance",
"Permanent": "無期限",
"Permanent Code": "永久コード",
"Permanent if empty": "空欄の場合は永久",
"Permanently Delete Account": "Permanently Delete Account",
"Permission Settings": "Permission Settings",
"Permission Tags": "Permission Tags",
@ -1016,12 +1038,13 @@
"Previous": "Previous",
"Price / Member": "Price / Member",
"Pricing Information": "Pricing Information",
"Print Invoice": "請求書印刷",
"Product": "商品",
"Product / Slot": "商品 / 貨道",
"Product / Stock": "商品 / 在庫",
"Product Count": "Product Count",
"Product Details": "商品細",
"Product ID": "商品ID",
"Product Count": "商品数量",
"Product Details": "商品細",
"Product ID": "商品番号",
"Product Image": "Product Image",
"Product Info": "Product Info",
"Product List": "Product List",
@ -1062,8 +1085,6 @@
"Quick Expiry Check": "Quick Expiry Check",
"Quick Maintenance": "Quick Maintenance",
"Quick Replenish": "クイック補充",
"Search logs...": "ログを検索...",
"Searchable Select": "Searchable Select",
"Quick replenishment from this view": "Quick replenishment from this view",
"Quick search...": "Quick search...",
"Real-time OEE analysis awaits": "Real-time OEE analysis awaits",
@ -1082,6 +1103,7 @@
"Recent Login": "Recent Login",
"Recently reported errors or warnings in logs": "Recently reported errors or warnings in logs",
"Records": "Records",
"Refunded": "返金済み",
"Regenerate": "再生成",
"Regenerating the token will disconnect the physical machine until it is updated. Continue?": "Regenerating the token will disconnect the physical machine until it is updated. Continue?",
"Remote Change": "Remote Change",
@ -1171,7 +1193,10 @@
"Scan to pick up your product": "商品を受け取るためにスキャンしてください",
"Schedule": "Schedule",
"Search Company Title...": "Search Company Title...",
"Search Flow ID / Slot...": "流水番号 / スロットを検索...",
"Search Invoice No / Flow ID...": "請求書番号 / 流水番号を検索...",
"Search Machine...": "Search Machine...",
"Search Order No / Flow ID / Invoice...": "注文番号 / フローID / 請求書番号で検索...",
"Search Product": "商品を検索",
"Search accounts...": "Search accounts...",
"Search by code, machine name or serial...": "コード、機台名、またはシリアルで検索...",
@ -1183,6 +1208,7 @@
"Search company...": "Search company...",
"Search configurations...": "Search configurations...",
"Search customers...": "Search customers...",
"Search logs...": "ログを検索...",
"Search machines by name or serial...": "Search machines by name or serial...",
"Search machines...": "機台を検索...",
"Search models...": "Search models...",
@ -1195,6 +1221,7 @@
"Search users...": "Search users...",
"Search warehouses...": "倉庫を検索...",
"Search...": "Search...",
"Searchable Select": "Searchable Select",
"Searching...": "Searching...",
"Seconds": "Seconds",
"Security & State": "Security & State",
@ -1251,6 +1278,7 @@
"Sign in to your account": "Sign in to your account",
"Signed in as": "Signed in as",
"Slot": "貨道",
"Slot / Product": "スロット / 商品",
"Slot Mechanism (default: Conveyor, check for Spring)": "Slot Mechanism (default: Conveyor, check for Spring)",
"Slot No": "Slot No",
"Slot Status": "Slot Status",
@ -1291,6 +1319,7 @@
"Start Time": "開始時間",
"Statistics": "Statistics",
"Status": "ステータス",
"Status / Invoice": "状態 / 請求書",
"Status / Temp / Sub / Card / Scan": "Status / Temp / Sub / Card / Scan",
"Status Timeline": "ステータスタイムライン",
"Status updated successfully": "ステータス更新成功",
@ -1304,6 +1333,7 @@
"Stock Level > 50%": "在庫 50% 以上",
"Stock Levels": "在庫レベル",
"Stock Management": "Stock Management",
"Stock Movement Log": "在庫異動ログ",
"Stock Out": "出庫",
"Stock Quantity": "Stock Quantity",
"Stock Rate": "在庫率",
@ -1314,6 +1344,7 @@
"Stock returned to warehouse": "在庫が倉庫に返却されました",
"Stock-In Management": "入庫管理",
"Stock-In Order": "入庫注文",
"Stock-In Order Details": "入庫伝票詳細",
"Stock-In Orders": "入庫伝票",
"Stock-In Orders Tab": "入庫伝票",
"Stock-in order": "入庫注文",
@ -1333,7 +1364,8 @@
"Sub-actions": "Sub-actions",
"Sub-machine Status Request": "Sub-machine Status Request",
"Submit Record": "Submit Record",
"Success": "Success",
"Subtotal": "小計",
"Success": "成功",
"Super Admin": "Super Admin",
"Super-admin role cannot be assigned to tenant accounts.": "Super-admin role cannot be assigned to tenant accounts.",
"Superseded": "Superseded",
@ -1394,7 +1426,7 @@
"To:": "To:",
"Today Cumulative Sales": "Today Cumulative Sales",
"Today's Transactions": "Today's Transactions",
"Total": "Total",
"Total": "合計",
"Total Connected": "Total Connected",
"Total Customers": "Total Customers",
"Total Daily Sales": "Total Daily Sales",
@ -1416,6 +1448,8 @@
"Track stock levels, stock-in orders, and movement history": "在庫レベル、入庫伝票、および移動履歴の追跡",
"Track stock-in orders and movement history": "入庫伝票および移動履歴の追跡",
"Traditional Chinese": "Traditional Chinese",
"Transaction Info": "取引情報",
"Transaction Time": "取引時間",
"Transaction processed: :id": "Transaction processed: :id",
"Transfer Audit": "転送監査",
"Transfer Details": "転送伝票詳細",
@ -1458,11 +1492,9 @@
"Unlock Page": "Unlock Page",
"Unpublish": "配信終了",
"Update": "更新",
"Edit Pass Code": "通行コード編集",
"Permanent if empty": "空欄の場合は永久",
"Update Pass Code": "通行コード更新",
"Update Authorization": "承認の更新",
"Update Customer": "Update Customer",
"Update Pass Code": "通行コード更新",
"Update Password": "Update Password",
"Update Product": "Update Product",
"Update Settings": "設定を更新",
@ -1499,12 +1531,14 @@
"View Inventory": "在庫を表示",
"View Logs": "View Logs",
"View More": "View More",
"View Order": "注文を表示",
"View QR Code": "QRコードを表示",
"View Slots": "貨道を表示",
"View all warehouses": "View all warehouses",
"View slot-level inventory for each machine": "View slot-level inventory for each machine",
"Visit Gift": "Visit Gift",
"Visual overview of all machine locations": "全機器位置のビジュアル概要",
"Void Invoice": "請求書無効化",
"Waiting": "Waiting",
"Waiting for Payment": "Waiting for Payment",
"Warehouse": "倉庫",
@ -1544,6 +1578,8 @@
"Welcome Gift Status": "Welcome Gift Status",
"Work Content": "作業内容",
"Yes, Cancel": "はい、キャンセルします",
"Yes, Deactivate": "はい、無効にします",
"Yes, Delete": "はい、削除します",
"Yes, Disable": "はい、無効にします",
"Yesterday": "Yesterday",
"You can assign or change the personnel handling this order": "この注文の担当者を割り当て、または変更できます",
@ -1554,6 +1590,7 @@
"accounts": "accounts",
"admin": "admin",
"analysis": "analysis",
"and :count other items": "他 :count 件の商品",
"app": "app",
"audit": "audit",
"basic-settings": "basic-settings",
@ -1561,6 +1598,8 @@
"basic.payment-configs": "basic.payment-configs",
"by": "by",
"companies": "companies",
"consume": "受取成功",
"consume_failed": "受取失敗",
"data-config": "data-config",
"data-config.sub-account-roles": "data-config.sub-account-roles",
"data-config.sub-accounts": "data-config.sub-accounts",
@ -1635,6 +1674,11 @@
"menu.warehouses.transfers": "在庫転送",
"min": "min",
"mins ago": "mins ago",
"movement.type.adjustment": "棚卸調整",
"movement.type.pickup": "受取コード消費",
"movement.type.remote_dispense": "リモート出庫",
"movement.type.replenishment": "補充入庫",
"movement.type.rollback": "出庫失敗ロールバック",
"of": "of",
"of items": "件のアイテム",
"pending": "pending",
@ -1656,12 +1700,10 @@
"to": "to",
"user": "user",
"vending": "vending",
"verify_success": "検証成功",
"video": "video",
"visit_gift": "visit_gift",
"vs Yesterday": "vs Yesterday",
"warehouses": "warehouses",
"待填寫": "待填寫",
"verify_success": "検証成功",
"consume": "受取成功",
"consume_failed": "受取失敗"
}
"待填寫": "待填寫"
}

View File

@ -74,6 +74,7 @@
"Affiliated Company": "公司名稱",
"Affiliated Unit": "公司名稱",
"Affiliation": "所屬單位",
"After Qty": "異動後數量",
"Alert Summary": "告警摘要",
"Alerts": "警示",
"Alerts Pending": "待處理告警",
@ -85,6 +86,7 @@
"All Levels": "所有層級",
"All Machines": "所有機台",
"All Permissions": "全部權限",
"All Slots": "所有貨道",
"All Stable": "狀態穩定",
"All Status": "所有狀態",
"All Statuses": "所有狀態",
@ -92,8 +94,10 @@
"All Times System Timezone": "所有時間為系統時區",
"All Types": "所有類型",
"All Warehouses": "所有倉庫",
"All issues marked as resolved.": "所有異常已標記為已排除。",
"All slots are fully stocked": "所有貨道已滿貨",
"Amount": "金額",
"Amount / Payment": "金額 / 支付",
"An error occurred while saving.": "儲存時發生錯誤。",
"Analysis Management": "分析管理",
"Analysis Permissions": "分析管理權限",
@ -133,6 +137,7 @@
"Are you sure you want to disable this pass code? It will no longer be valid for machine access.": "您確定要停用此通行碼嗎?停用後將無法再用於機台存取。",
"Are you sure you want to proceed? This action cannot be undone.": "您確定要繼續嗎?此操作將無法復原。",
"Are you sure you want to remove this assignment?": "您確定要移除此投放嗎?",
"Are you sure you want to resolve all recent issues for this machine?": "您確定要排除此機台目前所有的異常紀錄嗎?這將會恢復機台狀態為正常。",
"Are you sure you want to send this command?": "確定要發送此指令嗎?",
"Are you sure you want to start delivery?": "確定要開始配送嗎?",
"Are you sure?": "確定要執行此操作嗎?",
@ -146,6 +151,7 @@
"Assigned Machines": "授權機台",
"Assigned To": "指派人員",
"Assignment removed successfully.": "廣告投放已移除。",
"Associated Order": "關聯訂單",
"Audit Management": "稽核管理",
"Audit Permissions": "稽核管理權限",
"Authorization updated successfully": "授權更新成功",
@ -173,6 +179,7 @@
"Batch": "批號",
"Batch No": "批號",
"Batch Number": "批號",
"Before Qty": "異動前數量",
"Belongs To": "公司名稱",
"Belongs To Company": "公司名稱",
"Branch": "分倉",
@ -183,8 +190,8 @@
"CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "建立倉庫間調撥或機台退庫單",
"Calculate Replenishment": "計算補貨量",
"Cancel": "取消",
"Cancel Pass Code": "取消通行碼",
"Cancel Order": "取消訂單",
"Cancel Pass Code": "取消通行碼",
"Cancel Pickup Code": "取消取貨碼",
"Cancel Purchase": "取消購買",
"Cancelled": "已取消",
@ -225,6 +232,7 @@
"Checkout Time 1": "卡機結帳時間1",
"Checkout Time 2": "卡機結帳時間2",
"Clear": "清除",
"Clear Abnormal Status": "清除異常狀態",
"Clear Filter": "清除篩選",
"Clear Stock": "庫存清空",
"Click here to re-send the verification email.": "點擊此處重新發送驗證郵件。",
@ -371,6 +379,7 @@
"Delivery door closed": "送貨門關閉",
"Delivery door open error": "送貨門開啟異常",
"Delivery door opened": "送貨門開啟",
"Delta": "變化量",
"Deposit Bonus": "儲值回饋",
"Describe the repair or maintenance status...": "請描述維修或保養狀況...",
"Description / Name": "描述 / 名稱",
@ -389,8 +398,15 @@
"Disabled": "已停用",
"Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "停用此客戶將會連帶停用該客戶下的所有帳號,確定要繼續嗎?",
"Discord Notifications": "Discord通知",
"Discount": "折扣",
"Dispense Failed": "出貨失敗",
"Dispense Pending": "出貨中",
"Dispense Product": "出貨商品",
"Dispense Quantity": "出貨數量",
"Dispense Records": "出貨紀錄",
"Dispense Status": "出貨狀態",
"Dispense Success": "出貨成功",
"Dispense Time": "出貨時間",
"Dispense error (0407)": "出貨過程異常 (0407)",
"Dispense error (0408)": "出貨過程異常 (0408)",
"Dispense error (0409)": "出貨過程異常 (0409)",
@ -428,6 +444,7 @@
"Edit Machine Name": "編輯機台名稱",
"Edit Machine Settings": "編輯機台設定",
"Edit Name": "編輯名稱",
"Edit Pass Code": "編輯通行碼",
"Edit Payment Config": "編輯金流配置",
"Edit Product": "編輯商品",
"Edit Role": "編輯角色",
@ -437,6 +454,7 @@
"Edit Sub Account Role": "編輯子帳號角色",
"Edit Warehouse": "編輯倉庫",
"Electronic Invoice": "電子發票",
"Electronic Invoices": "電子發票",
"Elevator descending": "升降平台下降中",
"Elevator descent error": "升降平台下降異常",
"Elevator failure": "升降系統故障",
@ -476,6 +494,7 @@
"Error processing request": "處理請求時發生錯誤",
"Error report accepted": "錯誤回報已受理",
"Error: :message (:code)": "錯誤::message (:code)",
"Estimated Expiry": "預計到期時間",
"Execute": "執行",
"Execute Change": "執行找零",
"Execute Delivery Now": "立即執行出貨",
@ -483,7 +502,6 @@
"Execute maintenance and operational commands remotely": "遠端執行維護與各項營運指令",
"Execution Time": "執行時間",
"Exp": "效期",
"Estimated Expiry": "預計到期時間",
"Expected Expiry": "預計過期時間",
"Expected Expiry Date & Time": "預計到期時間",
"Expected Stock": "預期庫存",
@ -497,11 +515,12 @@
"Expiry Management": "效期管理",
"Expiry Time": "到期時間",
"Expiry date tracking and warnings": "有效期限追蹤與預警",
"Failed": "執行失敗",
"Failed": "失敗",
"Failed to fetch machine data.": "無法取得機台資料。",
"Failed to load permissions": "載入權限失敗",
"Failed to load preview": "載入預覽失敗",
"Failed to load tab content": "載入分頁內容失敗",
"Failed to resolve issues.": "排除異常失敗。",
"Failed to save permissions.": "無法儲存權限設定。",
"Failed to update machine images: ": "更新機台圖片失敗:",
"Feature Settings": "功能設定",
@ -516,6 +535,88 @@
"Firmware updated to :version": "韌體版本更新 : :version",
"Fleet Avg OEE": "全機台平均 OEE",
"Fleet Performance": "全機台效能",
"Flow ID": "流水號",
"Flow ID / Amount": "流水號 / 金額",
"Flow ID / Date": "流水號 / 日期",
"Flow ID / Machine": "流水號 / 機台",
"Flow ID / Order": "流水號 / 關聯訂單",
"Flow ID / Product": "流水號 / 商品",
"Flow ID / Slot": "流水號 / 貨道",
"Flow ID / Slot / Amount": "流水號 / 貨道 / 金額",
"Flow ID / Slot / Date": "流水號 / 貨道 / 日期",
"Flow ID / Slot / Machine": "流水號 / 貨道 / 機台",
"Flow ID / Slot / Order": "流水號 / 貨道 / 關聯訂單",
"Flow ID / Slot / Product": "流水號 / 貨道 / 商品",
"Flow ID / Slot / Product / Amount": "流水號 / 貨道 / 商品 / 金額",
"Flow ID / Slot / Product / Amount / Date": "流水號 / 貨道 / 商品 / 金額 / 日期",
"Flow ID / Slot / Product / Amount / Date / Order": "流水號 / 貨道 / 商品 / 金額 / 日期 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Date / Time": "流水號 / 貨道 / 商品 / 金額 / 日期 / 時間",
"Flow ID / Slot / Product / Amount / Date / Time / Order": "流水號 / 貨道 / 商品 / 金額 / 日期 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Machine": "流水號 / 貨道 / 商品 / 金額 / 機台",
"Flow ID / Slot / Product / Amount / Machine / Date": "流水號 / 貨道 / 商品 / 金額 / 機台 / 日期",
"Flow ID / Slot / Product / Amount / Machine / Date / Order": "流水號 / 貨道 / 商品 / 金額 / 機台 / 日期 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Machine / Date / Time": "流水號 / 貨道 / 商品 / 金額 / 機台 / 日期 / 時間",
"Flow ID / Slot / Product / Amount / Machine / Date / Time / Order": "流水號 / 貨道 / 商品 / 金額 / 機台 / 日期 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Machine / Order": "流水號 / 貨道 / 商品 / 金額 / 機台 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Machine / Time": "流水號 / 貨道 / 商品 / 金額 / 機台 / 時間",
"Flow ID / Slot / Product / Amount / Machine / Time / Order": "流水號 / 貨道 / 商品 / 金額 / 機台 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Order": "流水號 / 貨道 / 商品 / 金額 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Status": "流水號 / 貨道 / 商品 / 金額 / 狀態",
"Flow ID / Slot / Product / Amount / Status / Date": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 日期",
"Flow ID / Slot / Product / Amount / Status / Date / Order": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 日期 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Status / Date / Time": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 日期 / 時間",
"Flow ID / Slot / Product / Amount / Status / Date / Time / Order": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 日期 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Status / Machine": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 機台",
"Flow ID / Slot / Product / Amount / Status / Machine / Date": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 機台 / 日期",
"Flow ID / Slot / Product / Amount / Status / Machine / Date / Order": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 機台 / 日期 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Status / Machine / Date / Time": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 機台 / 日期 / 時間",
"Flow ID / Slot / Product / Amount / Status / Machine / Date / Time / Order": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 機台 / 日期 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Status / Machine / Date / Time / Order / Extra": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 機台 / 日期 / 時間 / 關聯訂單 / 額外",
"Flow ID / Slot / Product / Amount / Status / Machine / Date / Time / Order / Extra / More": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 機台 / 日期 / 時間 / 關聯訂單 / 額外 / 更多",
"Flow ID / Slot / Product / Amount / Status / Machine / Date / Time / Order / Extra / More / Done": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 機台 / 日期 / 時間 / 關聯訂單 / 額外 / 更多 / 完成",
"Flow ID / Slot / Product / Amount / Status / Machine / Order": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 機台 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Status / Machine / Time": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 機台 / 時間",
"Flow ID / Slot / Product / Amount / Status / Machine / Time / Order": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 機台 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Status / Order": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Status / Time": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 時間",
"Flow ID / Slot / Product / Amount / Status / Time / Order": "流水號 / 貨道 / 商品 / 金額 / 狀態 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Amount / Time": "流水號 / 貨道 / 商品 / 金額 / 時間",
"Flow ID / Slot / Product / Amount / Time / Order": "流水號 / 貨道 / 商品 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Date": "流水號 / 貨道 / 商品 / 日期",
"Flow ID / Slot / Product / Date / Order": "流水號 / 貨道 / 商品 / 日期 / 關聯訂單",
"Flow ID / Slot / Product / Date / Time": "流水號 / 貨道 / 商品 / 日期 / 時間",
"Flow ID / Slot / Product / Date / Time / Order": "流水號 / 貨道 / 商品 / 日期 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Machine": "流水號 / 貨道 / 商品 / 機台",
"Flow ID / Slot / Product / Machine / Date": "流水號 / 貨道 / 商品 / 機台 / 日期",
"Flow ID / Slot / Product / Machine / Date / Order": "流水號 / 貨道 / 商品 / 機台 / 日期 / 關聯訂單",
"Flow ID / Slot / Product / Machine / Date / Time": "流水號 / 貨道 / 商品 / 機台 / 日期 / 時間",
"Flow ID / Slot / Product / Machine / Date / Time / Order": "流水號 / 貨道 / 商品 / 機台 / 日期 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Machine / Order": "流水號 / 貨道 / 商品 / 機台 / 關聯訂單",
"Flow ID / Slot / Product / Machine / Time": "流水號 / 貨道 / 商品 / 機台 / 時間",
"Flow ID / Slot / Product / Machine / Time / Order": "流水號 / 貨道 / 商品 / 機台 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Order": "流水號 / 貨道 / 商品 / 關聯訂單",
"Flow ID / Slot / Product / Status": "流水號 / 貨道 / 商品 / 狀態",
"Flow ID / Slot / Product / Status / Date": "流水號 / 貨道 / 商品 / 狀態 / 日期",
"Flow ID / Slot / Product / Status / Date / Order": "流水號 / 貨道 / 商品 / 狀態 / 日期 / 關聯訂單",
"Flow ID / Slot / Product / Status / Date / Time": "流水號 / 貨道 / 商品 / 狀態 / 日期 / 時間",
"Flow ID / Slot / Product / Status / Date / Time / Order": "流水號 / 貨道 / 商品 / 狀態 / 日期 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Status / Machine": "流水號 / 貨道 / 商品 / 狀態 / 機台",
"Flow ID / Slot / Product / Status / Machine / Date": "流水號 / 貨道 / 商品 / 狀態 / 機台 / 日期",
"Flow ID / Slot / Product / Status / Machine / Date / Order": "流水號 / 貨道 / 商品 / 狀態 / 機台 / 日期 / 關聯訂單",
"Flow ID / Slot / Product / Status / Machine / Date / Time": "流水號 / 貨道 / 商品 / 狀態 / 機台 / 日期 / 時間",
"Flow ID / Slot / Product / Status / Machine / Date / Time / Order": "流水號 / 貨道 / 商品 / 狀態 / 機台 / 日期 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Status / Machine / Order": "流水號 / 貨道 / 商品 / 狀態 / 機台 / 關聯訂單",
"Flow ID / Slot / Product / Status / Machine / Time": "流水號 / 貨道 / 商品 / 狀態 / 機台 / 時間",
"Flow ID / Slot / Product / Status / Machine / Time / Order": "流水號 / 貨道 / 商品 / 狀態 / 機台 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Status / Order": "流水號 / 貨道 / 商品 / 狀態 / 關聯訂單",
"Flow ID / Slot / Product / Status / Time": "流水號 / 貨道 / 商品 / 狀態 / 時間",
"Flow ID / Slot / Product / Status / Time / Order": "流水號 / 貨道 / 商品 / 狀態 / 時間 / 關聯訂單",
"Flow ID / Slot / Product / Time": "流水號 / 貨道 / 商品 / 時間",
"Flow ID / Slot / Product / Time / Order": "流水號 / 貨道 / 商品 / 時間 / 關聯訂單",
"Flow ID / Slot / Status": "流水號 / 貨道 / 狀態",
"Flow ID / Slot / Time": "流水號 / 貨道 / 時間",
"Flow ID / Status": "流水號 / 狀態",
"Flow ID / Time": "流水號 / 時間",
"Force End Session": "強制結束當前會話",
"Force end current session": "強制結束目前的連線",
"From": "來源",
@ -535,6 +636,7 @@
"Gift Definitions": "禮品設定",
"Global roles accessible by all administrators.": "適用於所有管理者的全域角色。",
"Go Back": "返回",
"Go to E-Invoice": "前往電子發票",
"Goods & System Settings": "商品與系統設定",
"Got it": "知道了",
"Grant UI Access": "開放 UI 存取權限",
@ -579,7 +681,13 @@
"Inventory Overview": "庫存瀏覽",
"Inventory Stable": "庫存穩定",
"Inventory synced with machine": "庫存已與機台同步",
"Invoice Date": "開立日期",
"Invoice Information": "發票資訊",
"Invoice Number": "發票號碼",
"Invoice Number / Time": "發票號碼 / 時間",
"Invoice Number / Flow ID": "發票號碼 / 流水號",
"Invoice Status": "發票開立狀態",
"Issued At": "開立時間",
"Item List": "商品清單",
"Items": "筆",
"JKO_MERCHANT_ID": "街口支付 商店代號",
@ -615,12 +723,13 @@
"Line Products": "Line商品",
"LinePay": "LinePay",
"LinePay Payment": "LinePay 支付",
"Link": "連結",
"Link Copied": "連結已複製",
"Live Fleet Updates": "即時數據更新",
"Loading Cabinet...": "正在載入機台貨盤...",
"Loading Data": "載入資料中",
"Loading machines...": "正在載入機台...",
"Loading failed": "載入失敗",
"Loading machines...": "正在載入機台...",
"Loading...": "載入中...",
"Location": "位置",
"Location found!": "已成功獲取座標!",
@ -637,14 +746,16 @@
"Logout": "登出",
"Logs": "日誌",
"Longitude": "經度",
"Link": "連結",
"Low": "低",
"Low Stock": "庫存過低",
"Low Stock Alerts": "低庫存警示",
"Low stock and out-of-stock alerts": "低庫存與缺貨警示",
"Loyalty & Features": "行銷與點數",
"Machine": "機台",
"Machine / Date": "機台 / 日期",
"Machine / Slot": "機台 / 貨道",
"Machine Flow ID": "機台流水號",
"Machine Flow ID / Time": "機台流水號 / 時間",
"Machine Advertisement Settings": "機台廣告設置",
"Machine Count": "機台數量",
"Machine Details": "機台詳情",
@ -670,11 +781,13 @@
"Machine Reports": "機台報表",
"Machine Restart": "機台重啟",
"Machine Return": "機台退庫",
"Machine Serial": "機台序號",
"Machine Serial No": "機台序號",
"Machine Settings": "機台設定",
"Machine Status": "機台狀態",
"Machine Status List": "機台運行狀態列表",
"Machine Stock": "機台庫存",
"Machine Stock Movements": "機台庫存流水帳",
"Machine System Settings": "機台系統設定",
"Machine Utilization": "機台稼動率",
"Machine created successfully.": "機台已成功建立。",
@ -767,17 +880,6 @@
"Motor not stopped": "電機未停止",
"Movement History": "異動紀錄",
"Movement Logs": "異動紀錄",
"Machine Stock Movements": "機台庫存流水帳",
"Stock Movement Log": "庫存流水帳",
"movement.type.replenishment": "補貨入庫",
"movement.type.pickup": "取貨碼消耗",
"movement.type.remote_dispense": "遠端出貨",
"movement.type.adjustment": "盤點調整",
"movement.type.rollback": "出貨失敗退回",
"Before Qty": "異動前數量",
"After Qty": "異動後數量",
"Delta": "變化量",
"No movements found": "尚無異動紀錄",
"Multilingual Names": "多語系名稱",
"N/A": "不適用",
"Name": "名稱",
@ -809,6 +911,7 @@
"No advertisements found.": "未找到廣告素材。",
"No alert summary": "暫無告警記錄",
"No assignments": "尚未投放",
"No associated order found": "無關聯訂單",
"No categories found.": "找不到分類。",
"No command history": "尚無指令紀錄",
"No configurations found": "暫無相關配置",
@ -831,12 +934,15 @@
"No matching machines": "查無匹配機台",
"No materials available": "沒有可用的素材",
"No movement records found": "查無異動紀錄",
"No movements found": "尚無異動紀錄",
"No pass codes found": "找不到通行碼",
"No permissions": "無權限項目",
"No pickup codes found": "找不到取貨碼",
"No product record found": "無商品紀錄",
"No products found in this warehouse": "此倉庫目前無商品庫存",
"No products found matching your criteria.": "找不到符合條件的商品。",
"No records found": "未找到相關紀錄",
"No related detail found": "無關聯詳細資料",
"No replenishment orders found": "查無補貨單紀錄",
"No results found": "查無搜尋結果",
"No roles available": "目前沒有角色資料。",
@ -847,6 +953,7 @@
"No slots need replenishment": "無需補貨的貨道",
"No stock data found": "查無庫存資料",
"No stock-in orders found": "查無進貨單紀錄",
"No transaction orders found": "查無交易訂單",
"No transfer orders found": "查無調撥單紀錄",
"No users found": "找不到用戶資料",
"No warehouses found": "未找到倉庫",
@ -888,10 +995,14 @@
"Optional": "選填",
"Optional remarks": "選填備註",
"Optional remarks...": "選填備註...",
"Order Details": "進貨單詳情",
"Order Details": "單詳情",
"Order Info": "單據資訊",
"Order Items": "商品內容",
"Order Management": "訂單管理",
"Order No.": "單號",
"Order Number / Time": "訂單編號 / 時間",
"Order Status": "訂單狀態",
"Order Time": "訂單時間",
"Order already completed": "訂單已完成",
"Order already processed": "訂單已處理",
"Order completed and stock updated": "訂單已完成,機台庫存已更新",
@ -946,14 +1057,15 @@
"Pass Code QR": "通行碼 QR Code",
"Pass Code Ticket": "通行憑證",
"Pass Codes": "通行碼",
"Pass code cancelled.": "通行碼已取消。",
"Pass code created: :code": "通行碼已建立::code",
"Pass code deleted.": "通行碼已刪除",
"Pass code cancelled.": "通行碼已取消。",
"Pass code disabled.": "通行碼已取消。",
"Pass code updated.": "通行碼已更新",
"Password": "密碼",
"Password updated successfully.": "密碼已成功變更。",
"Payment & Invoice": "金流與發票",
"Payment Amount": "支付金額",
"Payment Buffer Seconds": "金流緩衝時間(s)",
"Payment Config": "金流配置",
"Payment Configuration": "客戶金流設定",
@ -961,11 +1073,13 @@
"Payment Configuration deleted successfully.": "金流設定已成功刪除。",
"Payment Configuration updated successfully.": "金流設定已成功更新。",
"Payment Selection": "付款選擇",
"Payment Type": "支付方式",
"Pending": "待處理",
"Per Page": "每頁顯示",
"Performance": "效能 (Performance)",
"Permanent": "永久",
"Permanent Code": "永久代碼",
"Permanent if empty": "留空則為永久有效",
"Permanently Delete Account": "永久刪除帳號",
"Permission Settings": "權限設定",
"Permission Tags": "權限標籤",
@ -977,9 +1091,9 @@
"Picked up": "領取",
"Picked up Time": "領取時間",
"Pickup Code": "取貨碼",
"Pickup Code (8 Digits)": "取貨碼 (8 位數)",
"Pickup Codes": "取貨碼",
"Pickup Ticket": "取貨憑證",
"Pickup Code (8 Digits)": "取貨碼 (8 位數)",
"Pickup code cancelled.": "取貨碼已取消",
"Pickup code generated: :code": "已生成取貨碼::code",
"Pickup code updated.": "取貨碼已更新",
@ -1014,6 +1128,7 @@
"Previous": "上一頁",
"Price / Member": "售價 / 會員價",
"Pricing Information": "價格資訊",
"Print Invoice": "列印發票",
"Product": "商品",
"Product / Slot": "商品 / 貨道",
"Product / Stock": "商品 / 庫存",
@ -1078,6 +1193,7 @@
"Recent Login": "最近登入",
"Recently reported errors or warnings in logs": "近期日誌中有錯誤或警告回報",
"Records": "筆",
"Refunded": "已退款",
"Regenerate": "重新產生",
"Regenerating the token will disconnect the physical machine until it is updated. Continue?": "重新產生金鑰將導致實體機台暫時失去連線,必須於機台端更新此新金鑰才能恢復。確定繼續嗎?",
"Remote Change": "遠端找零",
@ -1089,8 +1205,8 @@
"Remote Permissions": "遠端管理權限",
"Remote Reboot": "遠端結帳",
"Remote Settlement": "遠端結帳",
"Remote dispense successful for slot :slot": "貨道 :slot 遠端出貨成功",
"Remote dispense failed for slot :slot": "貨道 :slot 遠端出貨失敗",
"Remote dispense successful for slot :slot": "貨道 :slot 遠端出貨成功",
"Removal": "撤機",
"Repair": "維修",
"Replenish": "補貨",
@ -1168,9 +1284,12 @@
"Scan to pick up your product": "掃描以領取您的商品",
"Schedule": "排程區間",
"Search Company Title...": "搜尋公司名稱...",
"Search Flow ID / Slot...": "搜尋 流水號 / 貨道...",
"Search Invoice No / Flow ID...": "搜尋 發票號碼 / 流水號...",
"Search Machine...": "搜尋機台...",
"Search logs...": "搜尋紀錄...",
"Search Order No / Flow ID / Invoice...": "搜尋 訂單號 / 流水號 / 發票號碼...",
"Search Product": "搜尋商品",
"Search Product / Slot...": "搜尋商品名稱 / 貨道...",
"Search accounts...": "搜尋帳號...",
"Search by code, machine name or serial...": "搜尋代碼、機台名稱或序號...",
"Search by code, name or machine...": "搜尋代碼、名稱或機台...",
@ -1181,6 +1300,7 @@
"Search company...": "搜尋公司...",
"Search configurations...": "搜尋設定...",
"Search customers...": "搜尋客戶...",
"Search logs...": "搜尋紀錄...",
"Search machines by name or serial...": "搜尋機台名稱或序號...",
"Search machines...": "搜尋機台...",
"Search models...": "搜尋型號...",
@ -1249,6 +1369,7 @@
"Sign in to your account": "隨時隨地掌控您的業務。",
"Signed in as": "登入身份",
"Slot": "貨道",
"Slot / Product": "貨道 / 商品",
"Slot Mechanism (default: Conveyor, check for Spring)": "貨道機制 (預設履帶,勾選為彈簧)",
"Slot No": "貨道編號",
"Slot Status": "貨道效期",
@ -1289,6 +1410,7 @@
"Start Time": "開始時間",
"Statistics": "數據統計",
"Status": "狀態",
"Status / Invoice": "狀態 / 發票",
"Status / Temp / Sub / Card / Scan": "狀態 / 溫度 / 下位機 / 刷卡機 / 掃碼機",
"Status Timeline": "狀態時間線",
"Status updated successfully": "狀態更新成功",
@ -1302,6 +1424,7 @@
"Stock Level > 50%": "庫存高於 50%",
"Stock Levels": "庫存水準",
"Stock Management": "庫存管理單",
"Stock Movement Log": "庫存流水帳",
"Stock Out": "庫存出庫",
"Stock Quantity": "庫存數量",
"Stock Rate": "庫存率",
@ -1312,6 +1435,7 @@
"Stock returned to warehouse": "庫存已退回倉庫",
"Stock-In Management": "進貨管理",
"Stock-In Order": "新增進貨單",
"Stock-In Order Details": "進貨單詳情",
"Stock-In Orders": "進貨單管理",
"Stock-In Orders Tab": "進貨單管理",
"Stock-in order": "入庫單",
@ -1331,7 +1455,8 @@
"Sub-actions": "子項目",
"Sub-machine Status Request": "下位機狀態回傳",
"Submit Record": "提交紀錄",
"Success": "執行成功",
"Subtotal": "小計",
"Success": "成功",
"Super Admin": "超級管理員",
"Super-admin role cannot be assigned to tenant accounts.": "超級管理員角色無法指派給客戶帳號。",
"Superseded": "已取代",
@ -1414,6 +1539,9 @@
"Track stock levels, stock-in orders, and movement history": "追蹤庫存水位、進貨單與異動紀錄",
"Track stock-in orders and movement history": "追蹤進貨單據與庫存異動紀錄",
"Traditional Chinese": "繁體中文",
"Transaction Info": "交易資訊",
"Transaction Orders": "交易訂單",
"Transaction Time": "交易時間",
"Transaction processed: :id": "交易處理完成::id",
"Transfer Audit": "調撥單",
"Transfer Details": "調撥單詳情",
@ -1455,16 +1583,10 @@
"Unlock Now": "立即解鎖",
"Unlock Page": "頁面解鎖",
"Unpublish": "下架",
"cancel": "取消",
"create": "建立",
"update": "更新",
"used": "已使用",
"Update": "更新",
"Edit Pass Code": "編輯通行碼",
"Permanent if empty": "留空則為永久有效",
"Update Pass Code": "更新通行碼",
"Update Authorization": "更新授權",
"Update Customer": "更新客戶",
"Update Pass Code": "更新通行碼",
"Update Password": "更改密碼",
"Update Product": "更新商品",
"Update Settings": "更新設定",
@ -1492,21 +1614,25 @@
"Validity Period": "有效期限",
"Validity Period (Days)": "有效期限 (天)",
"Validity Period (Hours)": "有效期限 (小時)",
"Valid": "有效",
"Void": "作廢",
"Vending": "販賣頁",
"Vending Page": "販賣頁",
"Venue Management": "場地管理",
"Video": "影片",
"View Details": "查看細單",
"View Details": "查看詳情",
"View Full History": "查看完整歷程",
"View Inventory": "查看庫存",
"View Logs": "查看日誌",
"View More": "查看更多",
"View Order": "查看訂單",
"View QR Code": "查看 QR Code",
"View Slots": "查看貨道",
"View all warehouses": "查看所有倉庫",
"View slot-level inventory for each machine": "查看各機台貨道庫存",
"Visit Gift": "來店禮",
"Visual overview of all machine locations": "所有機台位置的視覺化概覽",
"Void Invoice": "作廢發票",
"Waiting": "等待中",
"Waiting for Payment": "等待付款",
"Warehouse": "倉庫",
@ -1557,13 +1683,26 @@
"accounts": "帳號管理",
"admin": "管理員",
"analysis": "分析管理",
"and :count other items": "等 :count 項商品",
"app": "APP 管理",
"audit": "稽核管理",
"basic-settings": "基本設定",
"basic.machines": "機台設定",
"basic.payment-configs": "客戶金流設定",
"by": "由",
"cancel": "取消",
"command.change": "找零",
"command.checkout": "結帳",
"command.dispense": "出貨",
"command.lock": "鎖機",
"command.reboot": "重啟",
"command.reboot_card": "讀卡機重啟",
"command.reload_stock": "同步庫存",
"command.unlock": "解鎖",
"companies": "客戶管理",
"consume": "取貨成功",
"consume_failed": "取貨失敗",
"create": "建立",
"data-config": "資料設定",
"data-config.sub-account-roles": "子帳號角色",
"data-config.sub-accounts": "子帳號管理",
@ -1588,6 +1727,15 @@
"items": "筆項目",
"john@example.com": "john@example.com",
"line": "Line 管理",
"log.command.failed": "遠端指令 [:type] 執行失敗",
"log.command.success": "遠端指令 [:type] 執行成功",
"log.passcode.verify_success_note": "維修人員於機台 [:machine_sn] 驗證通行碼 [:code]",
"log.pickup.consume_success": "[取貨碼] 核銷成功::code貨道:slot",
"log.pickup.consume_success_note": "機台 [:machine_sn] 已核銷取貨碼。貨道 :slot:old -> :new",
"log.pickup.dispense_failed": "[取貨碼] 機台回報出貨失敗::code",
"log.pickup.dispense_failed_note": "機台 [:machine_sn] 回報取貨碼 [:code] 出貨失敗",
"log.pickup.verify_success": "[取貨碼] 驗證成功,碼::code",
"log.pickup.verify_success_note": "機台 [:machine_sn] 已驗證取貨碼 [:code]",
"machines": "機台管理",
"members": "會員管理",
"menu.analysis": "數據分析",
@ -1638,6 +1786,13 @@
"menu.warehouses.transfers": "調撥單",
"min": "分",
"mins ago": "分鐘前",
"movement.note.manual_adjustment": "後台手動修改貨道庫存(舊: :old → 新: :new",
"movement.note.replenishment_correction": "B009 盤點回報修正(舊: :old → 新: :new",
"movement.type.adjustment": "盤點調整",
"movement.type.pickup": "取貨碼消耗",
"movement.type.remote_dispense": "遠端出貨",
"movement.type.replenishment": "補貨入庫",
"movement.type.rollback": "出貨失敗退回",
"of": "/共",
"of items": "筆項目",
"pending": "等待機台領取",
@ -1657,38 +1812,14 @@
"success": "成功",
"super-admin": "超級管理員",
"to": "至",
"update": "更新",
"used": "已使用",
"user": "一般用戶",
"vending": "販賣頁",
"verify_success": "驗證成功",
"video": "影片",
"visit_gift": "來店禮",
"vs Yesterday": "較昨日",
"warehouses": "倉庫管理",
"待填寫": "待填寫",
"verify_success": "驗證成功",
"consume": "取貨成功",
"consume_failed": "取貨失敗",
"All Slots": "所有貨道",
"log.pickup.verify_success": "[取貨碼] 驗證成功,碼::code",
"log.pickup.verify_success_note": "機台 [:machine_sn] 已驗證取貨碼 [:code]",
"log.pickup.consume_success": "[取貨碼] 核銷成功::code貨道:slot",
"log.pickup.consume_success_note": "機台 [:machine_sn] 已核銷取貨碼。貨道 :slot:old -> :new",
"log.pickup.dispense_failed": "[取貨碼] 機台回報出貨失敗::code",
"log.pickup.dispense_failed_note": "機台 [:machine_sn] 回報取貨碼 [:code] 出貨失敗",
"log.passcode.verify_success_note": "維修人員於機台 [:machine_sn] 驗證通行碼 [:code]",
"movement.note.manual_adjustment": "後台手動修改貨道庫存(舊: :old → 新: :new",
"movement.note.replenishment_correction": "B009 盤點回報修正(舊: :old → 新: :new",
"log.command.success": "遠端指令 [:type] 執行成功",
"log.command.failed": "遠端指令 [:type] 執行失敗",
"command.reboot": "重啟",
"command.reboot_card": "讀卡機重啟",
"command.lock": "鎖機",
"command.unlock": "解鎖",
"command.checkout": "結帳",
"command.change": "找零",
"command.dispense": "出貨",
"command.reload_stock": "同步庫存",
"Clear Abnormal Status": "清除異常狀態",
"Are you sure you want to resolve all recent issues for this machine?": "您確定要排除此機台目前所有的異常紀錄嗎?這將會恢復機台狀態為正常。",
"All issues marked as resolved.": "所有異常已標記為已排除。",
"Failed to resolve issues.": "排除異常失敗。"
}
"待填寫": "待填寫"
}

View File

@ -0,0 +1,203 @@
@extends('layouts.admin')
@section('content')
<div class="space-y-2 pb-20" x-data="salesCenter()"
@ajax:navigate.window.prevent="fetchTabData(activeTab, $event.detail.url)">
{{-- Page Header --}}
<x-page-header :title="$title" :subtitle="$description" />
{{-- Tab Navigation --}}
<x-tab-nav model="activeTab">
<x-tab-nav-item value="orders" :label="__('Transaction Orders')" model="activeTab" />
<x-tab-nav-item value="invoices" :label="__('Electronic Invoices')" model="activeTab" />
<x-tab-nav-item value="dispense" :label="__('Dispense Records')" model="activeTab" />
</x-tab-nav>
{{-- Tab Contents --}}
<div class="mt-6">
<div class="relative min-h-[400px]">
{{-- Orders Tab --}}
<div x-show="activeTab === 'orders'" class="relative"
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4"
x-transition:enter-end="opacity-100 translate-y-0">
<div class="luxury-card rounded-3xl p-8 relative overflow-hidden">
<x-luxury-spinner show="tabLoading === 'orders'" />
<div id="tab-orders-container" :class="{ 'opacity-30 pointer-events-none transition-opacity duration-300': tabLoading === 'orders' }">
@include('admin.sales.partials.tab-orders')
</div>
</div>
</div>
{{-- Invoices Tab --}}
<div x-show="activeTab === 'invoices'" class="relative"
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4"
x-transition:enter-end="opacity-100 translate-y-0" x-cloak>
<div class="luxury-card rounded-3xl p-8 relative overflow-hidden">
<x-luxury-spinner show="tabLoading === 'invoices'" />
<div id="tab-invoices-container" :class="{ 'opacity-30 pointer-events-none transition-opacity duration-300': tabLoading === 'invoices' }">
@include('admin.sales.partials.tab-invoices')
</div>
</div>
</div>
{{-- Dispense Records Tab --}}
<div x-show="activeTab === 'dispense'" class="relative"
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4"
x-transition:enter-end="opacity-100 translate-y-0" x-cloak>
<div class="luxury-card rounded-3xl p-8 relative overflow-hidden">
<x-luxury-spinner show="tabLoading === 'dispense'" />
<div id="tab-dispense-container" :class="{ 'opacity-30 pointer-events-none transition-opacity duration-300': tabLoading === 'dispense' }">
@include('admin.sales.partials.tab-dispense')
</div>
</div>
</div>
</div>
</div>
{{-- Slide-over Panel (Order Details) --}}
<div
class="fixed inset-0 z-[100] overflow-hidden"
x-show="showPanel"
x-cloak
@keydown.window.escape="showPanel = false"
>
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm transition-opacity"
x-show="showPanel"
x-transition:enter="ease-out duration-500"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-500"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="showPanel = false"
></div>
<div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
<div
class="pointer-events-auto w-screen max-w-2xl transform transition ease-in-out duration-500 sm:duration-700"
x-show="showPanel"
x-transition:enter="transform transition ease-in-out duration-500 sm:duration-700"
x-transition:enter-start="translate-x-full"
x-transition:enter-end="translate-x-0"
x-transition:leave="transform transition ease-in-out duration-500 sm:duration-700"
x-transition:leave-start="translate-x-0"
x-transition:leave-end="translate-x-full"
>
<div class="flex h-full flex-col overflow-y-scroll bg-white dark:bg-slate-900 shadow-2xl border-l border-slate-100 dark:border-slate-800">
<div id="order-detail-content">
{{-- AJAX injected content --}}
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
<script>
function salesCenter() {
return {
showPanel: false,
activeTab: '{{ $tab }}',
tabLoading: null,
init() {
const params = new URLSearchParams(window.location.search);
const urlTab = params.get('tab');
if (urlTab && ['orders', 'invoices', 'dispense'].includes(urlTab)) {
this.activeTab = urlTab;
}
// 監聽頁籤切換以更新網址列 (不觸發重整)
this.$watch('activeTab', (newTab) => {
const url = new URL(window.location.origin + window.location.pathname);
url.searchParams.set('tab', newTab);
window.history.pushState({}, '', url);
});
},
switchTab(tab, url = null) {
if (this.activeTab === tab && !url) return;
this.activeTab = tab;
// 如果有提供 URL (例如點擊跳轉),則使用 AJAX 載入新內容
if (url) {
this.fetchTabData(tab, url);
return;
}
},
async fetchTabData(tab, url = null) {
if (this.tabLoading === tab) return;
this.tabLoading = tab;
const container = document.getElementById('tab-' + tab + '-container');
if (!url) {
// 從表單建構 URL
let params = new URLSearchParams();
params.set('tab', tab);
if (container) {
const form = container.querySelector('form');
if (form) {
const formData = new FormData(form);
formData.forEach((value, key) => {
if (value && value.trim() !== '') params.append(key, value);
});
}
}
url = `${window.location.pathname}?${params.toString()}`;
}
try {
const response = await fetch(url, {
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json'
}
});
const data = await response.json();
if (data.success && data.html) {
if (container) container.innerHTML = data.html;
// 更新網址列但不重整頁面
window.history.pushState({}, '', url);
// 重新初始化 Preline 組件
this.$nextTick(() => {
if (window.HSStaticMethods) window.HSStaticMethods.autoInit();
});
}
} catch (err) {
console.error('Failed to fetch tab data:', err);
} finally {
this.tabLoading = null;
}
},
openDetail(orderId) {
this.showPanel = true;
document.getElementById('order-detail-content').innerHTML = `<div class="flex items-center justify-center h-full py-20"><x-luxury-spinner show="true" /></div>`;
fetch(`/admin/sales/${orderId}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(res => res.json())
.then(data => {
if (data.success) {
document.getElementById('order-detail-content').innerHTML = data.html;
}
})
.catch(err => {
console.error('Failed to load order detail:', err);
this.showPanel = false;
});
}
}
}
</script>
@endsection

View File

@ -0,0 +1,244 @@
{{-- 側滑面板標題 --}}
<div class="px-5 py-6 sm:px-8 border-b border-slate-200 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/50">
<div class="flex items-start justify-between gap-4">
<div class="min-w-0 flex-1">
<h2 class="text-xl sm:text-2xl font-black text-slate-800 dark:text-white font-display flex items-center gap-2 sm:gap-3">
<div class="p-2 bg-cyan-500/10 rounded-xl">
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-cyan-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" 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>
<span class="truncate">{{ __('Order Details') }}</span>
</h2>
<div class="mt-2 flex items-center gap-2 text-sm text-slate-500 dark:text-slate-400 font-bold uppercase tracking-widest">
<span class="text-cyan-600 dark:text-cyan-400 font-mono tracking-tighter text-lg">{{ $order->order_no }}</span>
</div>
</div>
<button type="button" @click="showPanel = false"
class="bg-white dark:bg-slate-800 rounded-full p-2 text-slate-400 hover:text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-700 focus:outline-none transition duration-300 shadow-sm border border-slate-200 dark:border-slate-700">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<div class="flex-1 overflow-y-auto p-6 sm:p-8 space-y-10">
{{-- 基本狀態卡片 --}}
<div class="grid grid-cols-2 gap-6 p-6 bg-slate-50/50 dark:bg-slate-800/30 rounded-[2rem] border border-slate-100 dark:border-slate-800">
<div class="space-y-2">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Order Status') }}</p>
<div>
@php
$statusMap = [
'pending' => 'pending',
'paid' => 'active',
'completed' => 'active',
'cancelled' => 'disabled',
'refunded' => 'error',
];
$labelMap = [
'pending' => __('Pending'),
'paid' => __('Paid'),
'completed' => __('Completed'),
'cancelled' => __('Cancelled'),
'refunded' => __('Refunded'),
];
$status = $statusMap[$order->status] ?? 'slate';
$label = $labelMap[$order->status] ?? $order->status;
@endphp
<x-status-badge :status="$status" :label="$label" size="sm" />
</div>
</div>
<div class="space-y-2">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Payment Type') }}</p>
<p class="text-sm font-extrabold text-slate-700 dark:text-slate-200">
{{ $paymentTypes[$order->payment_type] ?? __('Unknown') }}
</p>
</div>
</div>
{{-- 交易資訊 --}}
<section class="space-y-4">
<div class="flex items-center gap-2 px-1">
<div class="w-1.5 h-4 bg-cyan-500 rounded-full"></div>
<h3 class="text-sm font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Transaction Info') }}</h3>
</div>
<div class="grid grid-cols-2 gap-8 p-8 bg-slate-50/50 dark:bg-slate-800/30 rounded-[2rem] border border-slate-100 dark:border-slate-800">
<div class="space-y-1">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Transaction Time') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 font-mono tracking-tight">{{ $order->created_at->format('Y-m-d H:i:s') }}</p>
</div>
<div class="space-y-1">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Machine Name') }}</p>
<p class="text-sm font-extrabold text-slate-700 dark:text-slate-200">{{ $order->machine->name ?? __('Unknown') }}</p>
</div>
<div class="space-y-1">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Machine Serial') }}</p>
<p class="text-xs font-black text-slate-400 dark:text-slate-500 font-mono uppercase tracking-widest">{{ $order->machine->serial_no ?? '---' }}</p>
</div>
<div class="space-y-1">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Machine Flow ID') }}</p>
<p class="text-xs font-black text-slate-400 dark:text-slate-500 font-mono uppercase tracking-widest">{{ $order->flow_id }}</p>
</div>
</div>
</section>
{{-- 商品明細 --}}
<section class="space-y-4">
<div class="flex items-center justify-between px-1">
<div class="flex items-center gap-2">
<div class="w-1.5 h-4 bg-cyan-500 rounded-full"></div>
<h3 class="text-sm font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Product Details') }}</h3>
</div>
<span class="text-[10px] font-black text-cyan-500 bg-cyan-500/10 px-2 py-0.5 rounded-md uppercase tracking-widest">{{ $order->items->count() }} {{ __('Items') }}</span>
</div>
<div class="space-y-3">
@foreach($order->items as $item)
<div class="luxury-card p-4 rounded-2xl flex items-center gap-4 group/item transition-all hover:border-cyan-500/30">
<div class="w-16 h-16 rounded-xl bg-slate-100 dark:bg-slate-800 flex-shrink-0 overflow-hidden border border-slate-200 dark:border-slate-700 group-hover:scale-105 transition-transform flex items-center justify-center">
@if(isset($item->product->image_url) && $item->product->image_url)
<img src="{{ $item->product->image_url }}" class="w-full h-full object-cover">
@else
<svg class="w-8 h-8 text-slate-300 group-hover:text-cyan-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"/>
</svg>
@endif
</div>
<div class="flex-1 min-w-0">
<p class="text-base font-extrabold text-slate-800 dark:text-white truncate tracking-tight">{{ $item->product_name }}</p>
<div class="flex items-center gap-3 mt-1">
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">${{ number_format($item->price, 0) }} × {{ $item->quantity }}</span>
</div>
</div>
<div class="text-right">
<p class="text-base font-black text-slate-800 dark:text-white tracking-tight">${{ number_format($item->subtotal, 0) }}</p>
</div>
</div>
@endforeach
</div>
{{-- 金額彙總 --}}
<div class="mt-6 p-8 rounded-[2rem] bg-slate-50/50 dark:bg-slate-800/30 border border-slate-100 dark:border-slate-800 relative overflow-hidden group">
<!-- Decorative Gradient -->
<div class="absolute top-0 right-0 w-32 h-32 bg-cyan-500/10 blur-[50px] -mr-16 -mt-16 group-hover:bg-cyan-500/20 transition-colors duration-500"></div>
<div class="relative space-y-4">
<div class="flex justify-between items-center text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em]">
<span>{{ __('Subtotal') }}</span>
<span class="font-mono text-slate-600 dark:text-slate-300">${{ number_format($order->total_amount + $order->discount_amount, 0) }}</span>
</div>
<div class="flex justify-between items-center text-[10px] font-black text-rose-500 uppercase tracking-[0.2em]">
<span>{{ __('Discount') }}</span>
<span class="font-mono">-${{ number_format($order->discount_amount, 0) }}</span>
</div>
<div class="pt-4 mt-2 border-t border-slate-100 dark:border-slate-800/50 flex justify-between items-baseline">
<span class="text-sm font-black uppercase tracking-[0.2em] text-slate-500 dark:text-slate-400">{{ __('Total') }}</span>
<div class="text-right">
<span class="text-3xl font-black text-cyan-600 dark:text-cyan-400 tracking-tighter font-display">${{ number_format($order->total_amount, 0) }}</span>
</div>
</div>
</div>
</div>
</section>
{{-- 出貨紀錄 --}}
@if($order->dispenseRecords->count() > 0)
<section class="space-y-4">
<div class="flex items-center justify-between px-1">
<div class="flex items-center gap-2">
<div class="w-1.5 h-4 bg-emerald-500 rounded-full"></div>
<h3 class="text-sm font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Dispense Status') }}</h3>
</div>
</div>
<div class="bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 rounded-[2rem] overflow-hidden shadow-sm">
<table class="w-full text-left border-separate border-spacing-y-0">
<thead>
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
<th class="px-6 py-4 text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Slot') }}</th>
<th class="px-6 py-4 text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Product') }}</th>
<th class="px-6 py-4 text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Status') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
@foreach($order->dispenseRecords as $dispense)
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200">
<td class="px-6 py-4">
<span class="text-xs font-black text-cyan-600 dark:text-cyan-400 font-mono">#{{ str_pad($dispense->slot_no, 2, '0', STR_PAD_LEFT) }}</span>
</td>
<td class="px-6 py-4">
<p class="text-sm font-bold text-slate-700 dark:text-slate-200">{{ $dispense->product->name ?? __('Unknown') }}</p>
</td>
<td class="px-6 py-4 text-right">
@php
$dStatusMap = [
'success' => 'active',
'failed' => 'error',
'pending' => 'pending',
'1' => 'active',
'0' => 'error',
];
$dLabelMap = [
'success' => __('Success'),
'failed' => __('Failed'),
'pending' => __('Pending'),
'1' => __('Success'),
'0' => __('Failed'),
];
$statusValue = (string)($dispense->dispense_status ?? 'pending');
$ds = $dStatusMap[$statusValue] ?? 'slate';
$dl = $dLabelMap[$statusValue] ?? $statusValue;
@endphp
<x-status-badge :status="$ds" :label="$dl" size="xs" />
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</section>
@endif
{{-- 發票資訊 --}}
@if($order->invoice)
<section class="space-y-4">
<div class="flex items-center gap-2 px-1">
<div class="w-1.5 h-4 bg-indigo-500 rounded-full"></div>
<h3 class="text-sm font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Invoice Information') }}</h3>
</div>
<div class="bg-indigo-50/30 dark:bg-indigo-500/5 border border-indigo-100 dark:border-indigo-500/20 rounded-[2rem] p-8 group relative overflow-hidden">
<!-- Decorative Accent -->
<div class="absolute top-0 right-0 p-8 opacity-10 group-hover:opacity-20 transition-opacity duration-500">
<svg class="w-24 h-24 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" 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="relative flex flex-col sm:flex-row sm:items-center justify-between gap-6">
<div class="space-y-3">
<div class="flex items-center gap-3">
<p class="text-xl font-black text-slate-800 dark:text-white font-mono tracking-tighter">{{ $order->invoice->invoice_no }}</p>
<x-status-badge status="active" :label="__('Valid')" size="xs" />
</div>
<div class="flex flex-col gap-1">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Issued At') }}</p>
<p class="text-sm font-bold text-slate-600 dark:text-slate-400 font-mono tracking-tight">
{{ $order->invoice->invoice_date }} {{ $order->invoice->invoice_time }}
</p>
</div>
</div>
<div class="flex items-center gap-3">
<button class="flex-1 sm:flex-none px-6 py-3 rounded-xl bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 font-bold text-xs border border-slate-200 dark:border-slate-700 hover:bg-indigo-500 hover:text-white hover:border-indigo-500 transition-all duration-300 shadow-sm">
{{ __('Print Invoice') }}
</button>
<button class="flex-1 sm:flex-none px-6 py-3 rounded-xl bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 font-bold text-xs border border-slate-200 dark:border-slate-700 hover:bg-rose-500 hover:text-white hover:border-rose-500 transition-all duration-300 shadow-sm">
{{ __('Void Invoice') }}
</button>
</div>
</div>
</div>
</section>
@endif
</div>

View File

@ -0,0 +1,274 @@
{{-- 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')">
<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">
<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="{{ $filters['search'] }}"
class="py-2.5 pl-12 pr-6 block w-full luxury-input"
placeholder="{{ __('Search Product / Slot...') }}">
</div>
<div class="w-full lg:w-48">
<x-searchable-select name="machine_id" :placeholder="__('All Machines')" :selected="$filters['machine_id']"
@change="$el.closest('form').dispatchEvent(new Event('submit'))">
@foreach($machines as $m)
<option value="{{ $m->id }}" {{ $filters['machine_id'] == $m->id ? 'selected' : '' }} data-title="{{ $m->name }}">
{{ $m->name }}
</option>
@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, {
mode: 'range',
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
defaultHour: 0,
defaultMinute: 0,
locale: '{{ app()->getLocale() == 'zh_TW' ? 'zh_tw' : 'en' }}',
defaultDate: '{{ $filters['start_date'] }}' && '{{ $filters['end_date'] }}' ? ['{{ $filters['start_date'] }}', '{{ $filters['end_date'] }}'] : [],
onChange: function(selectedDates, dateStr, instance) {
if (selectedDates.length === 2) {
$refs.startDate.value = instance.formatDate(selectedDates[0], 'Y-m-d H:i');
$refs.endDate.value = instance.formatDate(selectedDates[1], 'Y-m-d H:i');
$el.closest('form').dispatchEvent(new Event('submit'));
} else if (selectedDates.length === 0) {
$refs.startDate.value = '';
$refs.endDate.value = '';
$el.closest('form').dispatchEvent(new Event('submit'));
}
}
})">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10 text-slate-400 group-focus-within:text-cyan-500 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
</span>
<input type="hidden" name="start_date" x-ref="startDate" value="{{ $filters['start_date'] }}">
<input type="hidden" name="end_date" x-ref="endDate" value="{{ $filters['end_date'] }}">
<input type="text" x-ref="dateRange"
value="{{ $filters['start_date'] && $filters['end_date'] ? $filters['start_date'] . ' 至 ' . $filters['end_date'] : ($filters['start_date'] ?: '') }}"
placeholder="{{ __('Select Date Range') }}" class="luxury-input py-2.5 pl-12 pr-6 block w-full text-sm font-bold cursor-pointer">
</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>
</button>
<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">
<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>
</button>
</div>
</form>
{{-- Table (Desktop) --}}
<div class="hidden xl:block overflow-x-auto">
<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">
{{ __('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">
{{ __('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">
{{ __('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">
{{ __('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">
{{ __('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">
{{ __('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>
@empty
<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>
</div>
{{-- 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
{{-- 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>
</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 ?? '---' }}
</p>
</div>
</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
</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>

View File

@ -0,0 +1,283 @@
{{-- 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')">
<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">
<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="{{ $filters['search'] }}"
class="py-2.5 pl-12 pr-6 block w-full luxury-input"
placeholder="{{ __('Search Invoice No / Flow ID...') }}">
</div>
<div class="w-full lg:w-48">
<x-searchable-select name="machine_id" :placeholder="__('All Machines')" :selected="$filters['machine_id']"
@change="$el.closest('form').dispatchEvent(new Event('submit'))">
@foreach($machines as $m)
<option value="{{ $m->id }}" {{ $filters['machine_id'] == $m->id ? 'selected' : '' }} data-title="{{ $m->name }}">
{{ $m->name }}
</option>
@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, {
mode: 'range',
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
defaultHour: 0,
defaultMinute: 0,
locale: '{{ app()->getLocale() == 'zh_TW' ? 'zh_tw' : 'en' }}',
defaultDate: '{{ $filters['start_date'] }}' && '{{ $filters['end_date'] }}' ? ['{{ $filters['start_date'] }}', '{{ $filters['end_date'] }}'] : [],
onChange: function(selectedDates, dateStr, instance) {
if (selectedDates.length === 2) {
$refs.startDate.value = instance.formatDate(selectedDates[0], 'Y-m-d H:i');
$refs.endDate.value = instance.formatDate(selectedDates[1], 'Y-m-d H:i');
$el.closest('form').dispatchEvent(new Event('submit'));
} else if (selectedDates.length === 0) {
$refs.startDate.value = '';
$refs.endDate.value = '';
$el.closest('form').dispatchEvent(new Event('submit'));
}
}
})">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10 text-slate-400 group-focus-within:text-cyan-500 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
</span>
<input type="hidden" name="start_date" x-ref="startDate" value="{{ $filters['start_date'] }}">
<input type="hidden" name="end_date" x-ref="endDate" value="{{ $filters['end_date'] }}">
<input type="text" x-ref="dateRange"
value="{{ $filters['start_date'] && $filters['end_date'] ? $filters['start_date'] . ' 至 ' . $filters['end_date'] : ($filters['start_date'] ?: '') }}"
placeholder="{{ __('Select Date Range') }}" class="luxury-input py-2.5 pl-12 pr-6 block w-full text-sm font-bold cursor-pointer">
</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>
</button>
<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">
<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>
</button>
</div>
</form>
{{-- Table (Desktop) --}}
<div class="hidden xl:block overflow-x-auto">
<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">
{{ __('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">
{{ __('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">
{{ __('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">
{{ __('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">
{{ __('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">
{{ __('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">
{{ __('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 }}
</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>
@endforelse
</tbody>
</table>
</div>
{{-- 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
{{-- 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" />
</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>

View File

@ -0,0 +1,348 @@
{{-- 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('orders')">
<input type="hidden" name="tab" value="orders">
<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">
<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="{{ $filters['search'] }}"
class="py-2.5 pl-12 pr-6 block w-full luxury-input"
placeholder="{{ __('Search Order No / Flow ID / Invoice...') }}">
</div>
<div class="w-full lg:w-48">
<x-searchable-select name="machine_id" :placeholder="__('All Machines')" :selected="$filters['machine_id']"
@change="$el.closest('form').dispatchEvent(new Event('submit'))">
@foreach($machines as $m)
<option value="{{ $m->id }}" {{ $filters['machine_id']==$m->id ? 'selected' : '' }} data-title="{{ $m->name
}}">
{{ $m->name }}
</option>
@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, {
mode: 'range',
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
defaultHour: 0,
defaultMinute: 0,
locale: '{{ app()->getLocale() == 'zh_TW' ? 'zh_tw' : 'en' }}',
defaultDate: '{{ $filters['start_date'] }}' && '{{ $filters['end_date'] }}' ? ['{{ $filters['start_date'] }}', '{{ $filters['end_date'] }}'] : [],
onChange: function(selectedDates, dateStr, instance) {
if (selectedDates.length === 2) {
$refs.startDate.value = instance.formatDate(selectedDates[0], 'Y-m-d H:i');
$refs.endDate.value = instance.formatDate(selectedDates[1], 'Y-m-d H:i');
$el.closest('form').dispatchEvent(new Event('submit'));
} else if (selectedDates.length === 0) {
$refs.startDate.value = '';
$refs.endDate.value = '';
$el.closest('form').dispatchEvent(new Event('submit'));
}
}
})">
<span
class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10 text-slate-400 group-focus-within:text-cyan-500 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</span>
<input type="hidden" name="start_date" x-ref="startDate" value="{{ $filters['start_date'] }}">
<input type="hidden" name="end_date" x-ref="endDate" value="{{ $filters['end_date'] }}">
<input type="text" x-ref="dateRange"
value="{{ $filters['start_date'] && $filters['end_date'] ? $filters['start_date'] . ' 至 ' . $filters['end_date'] : ($filters['start_date'] ?: '') }}"
placeholder="{{ __('Select Date Range') }}"
class="luxury-input py-2.5 pl-12 pr-6 block w-full text-sm font-bold cursor-pointer">
</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>
</button>
<button type="button" @click="switchTab('orders', '{{ route('admin.sales.index', ['tab' => 'orders']) }}')"
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">
<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>
</button>
</div>
</form>
{{-- Table (Desktop) --}}
<div class="hidden xl:block overflow-x-auto">
<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">
{{ __('Order Number / 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">
{{ __('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">
{{ __('Product') }}
</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">
{{ __('Payment 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">
{{ __('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">
{{ __('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 text-right">
{{ __('Action') }}
</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-800/80">
@forelse($orders as $order)
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200">
<td class="px-6 py-6">
<div class="flex flex-col">
<span @click.stop="openDetail({{ $order->id }})"
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 cursor-pointer">
{{ $order->order_no }}
</span>
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1">
{{ $order->created_at->format('Y-m-d H:i:s') }}
</span>
</div>
</td>
<td class="px-6 py-6">
<div class="text-sm font-extrabold text-slate-800 dark:text-slate-100 tracking-tight">
{{ $order->machine->name ?? 'Unknown' }}
</div>
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em]">
{{ $order->machine->serial_no ?? '---' }}
</div>
</td>
<td class="px-6 py-6">
<div class="flex flex-col max-w-[200px]">
@if($order->items->count() > 0)
<span class="text-sm font-black text-cyan-600 dark:text-cyan-400 mb-0.5 truncate">
{{ $order->items->first()->product_name }}
</span>
@if($order->items->count() > 1)
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">
{{ __('and :count other items', ['count' => $order->items->count()]) }}
</span>
@endif
@else
<span class="text-sm text-slate-400 italic">{{ __('No product record found') }}</span>
@endif
</div>
</td>
<td class="px-6 py-6">
<div class="flex flex-col">
<div class="flex items-center gap-1.5">
<span class="text-base font-black text-slate-800 dark:text-white tracking-tight">
${{ number_format($order->total_amount, 0) }}
</span>
<span
class="text-[10px] px-2 py-0.5 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-500 font-black uppercase tracking-widest border border-slate-200 dark:border-slate-700">
{{ $paymentTypes[$order->payment_type] ?? 'Unknown' }}
</span>
</div>
@if($order->discount_amount > 0)
<span class="text-[10px] text-rose-500 font-bold uppercase tracking-widest mt-1">
{{ __('Discounted') }} ${{ number_format($order->discount_amount, 0) }}
</span>
@endif
</div>
</td>
<td class="px-6 py-6 whitespace-nowrap">
@php
$statusMap = [
'pending' => ['label' => __('Pending'), 'color' => 'amber'],
'paid' => ['label' => __('Paid'), 'color' => 'emerald'],
'completed' => ['label' => __('Completed'), 'color' => 'emerald'],
'cancelled' => ['label' => __('Cancelled'), 'color' => 'slate'],
'refunded' => ['label' => __('Refunded'), 'color' => 'rose'],
];
$s = $statusMap[$order->status] ?? ['label' => $order->status, 'color' => 'slate'];
$invoiceSearchValue = $order->invoice ? $order->invoice->invoice_no : $order->order_no;
$targetUrl = route('admin.sales.index', ['tab' => 'invoices', 'search' => $invoiceSearchValue]);
@endphp
<x-status-badge :color="$s['color']" :label="$s['label']" size="xs" />
</td>
<td class="px-6 py-6 whitespace-nowrap">
@if($order->invoice)
<div @click.stop="switchTab('invoices', '{{ $targetUrl }}')"
class="flex items-center gap-1.5 text-sm font-extrabold text-slate-600 dark:text-slate-400 cursor-pointer hover:text-cyan-500 transition-colors group/invoice"
title="{{ __('Go to E-Invoice') }}">
<svg class="w-4 h-4 text-slate-300 dark:text-slate-600 group-hover/invoice:text-cyan-500"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5"
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>
<span class="group-hover/invoice:underline decoration-2 underline-offset-4">{{
$order->invoice->invoice_no }}</span>
</div>
@else
<span class="text-xs font-bold text-slate-300 dark:text-slate-700 tracking-widest">---</span>
@endif
</td>
<td class="px-6 py-6 text-right">
<button @click.stop="openDetail({{ $order->id }})"
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 group/btn"
title="{{ __('View Details') }}">
<svg class="w-4 h-4 stroke-[2.5] transition-transform group-hover/btn:scale-110" 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="7" class="py-20 text-center">
<x-empty-state mode="table" colspan="7" :message="__('No transaction orders found')" />
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- Mobile Cards (xl:hidden) --}}
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
@forelse($orders as $order)
<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 = [
'pending' => ['label' => __('Pending'), 'color' => 'amber'],
'paid' => ['label' => __('Paid'), 'color' => 'emerald'],
'completed' => ['label' => __('Completed'), 'color' => 'emerald'],
'cancelled' => ['label' => __('Cancelled'), 'color' => 'slate'],
'refunded' => ['label' => __('Refunded'), 'color' => 'rose'],
];
$s = $statusMap[$order->status] ?? ['label' => $order->status, 'color' => 'slate'];
$invoiceSearchValue = $order->invoice ? $order->invoice->invoice_no : $order->order_no;
$targetUrl = route('admin.sales.index', ['tab' => 'invoices', 'search' => $invoiceSearchValue]);
@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="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" />
</svg>
</div>
<div class="min-w-0">
<h3 @click="openDetail({{ $order->id }})"
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 cursor-pointer">
{{ $order->order_no }}
</h3>
<p
class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest truncate">
{{ $order->machine->name ?? 'Unknown' }}
</p>
</div>
</div>
<div @click.stop="switchTab('invoices', '{{ $targetUrl }}')" class="cursor-pointer">
<x-status-badge :color="$s['color']" :label="$s['label']" size="sm" />
</div>
</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">{{
__('Order Items') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 truncate">
@if($order->items->count() > 0)
{{ $order->items->first()->product_name }}
@if($order->items->count() > 1)
<span class="text-[10px] text-slate-400 font-bold ml-1">{{ __('and :count other items', ['count' =>
$order->items->count()]) }}</span>
@endif
@else
<span class="text-slate-400 italic">{{ __('No product record found') }}</span>
@endif
</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Amount / Payment') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 flex items-center gap-1.5">
<span class="text-cyan-600 dark:text-cyan-400">${{ number_format($order->total_amount, 0) }}</span>
<span class="text-[10px] px-1.5 py-0.5 rounded bg-slate-100 dark:bg-slate-800/50 text-slate-500">{{
$paymentTypes[$order->payment_type] ?? '??' }}</span>
</p>
</div>
<div>
<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') }}
<span class="text-[10px] text-slate-400 ml-1">{{ $order->created_at->diffForHumans() }}</span>
</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{
__('Invoice Number') }}</p>
<p @click.stop="switchTab('invoices', '{{ $targetUrl }}')"
class="text-sm font-bold text-slate-700 dark:text-slate-300 cursor-pointer hover:text-cyan-500 transition-colors">
{{ $order->invoice->invoice_no ?? '---' }}
</p>
</div>
</div>
{{-- Action Buttons --}}
<div class="flex items-center gap-3">
<button @click="openDetail({{ $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 transaction orders found')" />
</div>
@endforelse
</div>
{{-- Pagination --}}
<div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
{{ $orders->links('vendor.pagination.luxury', ['page_param' => 'orders_page']) }}
</div>

View File

@ -69,7 +69,7 @@
</tr>
@else
{{-- 獨立 Card 版(用於 card grid @empty,或 Alpine x-if 包裹) --}}
<div class="col-span-full py-20 text-center flex flex-col items-center gap-3 luxury-card rounded-3xl border border-slate-100 dark:border-slate-800/50 bg-white/50 dark:bg-slate-900/50">
<div class="col-span-full py-20 text-center flex flex-col items-center gap-3">
<div class="p-4 rounded-3xl bg-slate-50 dark:bg-slate-800/50">
@if($slot->isNotEmpty())
{{ $slot }}

View File

@ -20,11 +20,12 @@
<button
type="button"
{{ $attributes }}
@click="{{ $model }} = '{{ $value }}'"
:class="{{ $model }} === '{{ $value }}'
? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-sm shadow-cyan-500/10'
: 'text-slate-400 hover:text-slate-600 dark:hover:text-slate-200'"
class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest transition-all duration-300 flex-1 sm:flex-none"
class="px-6 py-3 rounded-xl text-sm font-black uppercase tracking-widest transition-all duration-300 flex-1 sm:flex-none"
>
{{ $label }}
</button>

View File

@ -11,8 +11,9 @@
$limits = [10, 25, 50, 100];
$event = $ajax_navigate_event ?? 'ajax:navigate';
$perPageKey = $per_page_param ?? 'per_page';
$pageKey = $page_param ?? 'page';
@endphp
<select onchange="{ const params = new URLSearchParams(window.location.search); params.set('{{ $perPageKey }}', this.value); params.delete('page'); const url = window.location.pathname + '?' + params.toString(); const evt = new CustomEvent('{{ $event }}', { detail: { url: url }, bubbles: true, cancelable: true }); if (this.dispatchEvent(evt)) { window.location.href = url; } }"
<select onchange="{ const params = new URLSearchParams(window.location.search); params.set('{{ $perPageKey }}', this.value); params.delete('{{ $pageKey }}'); const url = window.location.pathname + '?' + params.toString(); const evt = new CustomEvent('{{ $event }}', { detail: { url: url }, bubbles: true, cancelable: true }); if (this.dispatchEvent(evt)) { window.location.href = url; } }"
class="h-7 pl-2 pr-7 rounded-lg bg-white dark:bg-slate-800 border-none text-[11px] font-black text-slate-600 dark:text-slate-300 appearance-none focus:ring-4 focus:ring-cyan-500/10 outline-none transition-all cursor-pointer shadow-sm leading-none py-0 !bg-none">
@foreach($limits as $l)
<option value="{{ $l }}" {{ $currentLimit == $l ? 'selected' : '' }}>{{ $l }}</option>

View File

@ -138,6 +138,9 @@ Route::middleware(['auth', 'verified', 'tenant.access'])->prefix('admin')->name(
Route::get('/orders', [App\Http\Controllers\Admin\SalesController::class, 'orders'])->name('orders');
Route::get('/promotions', [App\Http\Controllers\Admin\SalesController::class, 'promotions'])->name('promotions');
Route::get('/store-gifts', [App\Http\Controllers\Admin\SalesController::class, 'storeGifts'])->name('store-gifts');
// 詳情路由必須放在最後,避免攔截其他具體路由
Route::get('/{order}', [App\Http\Controllers\Admin\SalesController::class, 'show'])->name('show');
});
// 7. 分析管理