[STYLE] 優化機台設定與倉庫管理介面規範化
1. 機台管理模組優化: - 移除機台設定內部的「機台分布」標籤頁,改為標題旁獨立的極簡白色連結入口。 - 優化機台列表欄位,將「公司名稱」獨立顯示。 - 實作機台系統設定非同步更新功能 (update-system-settings)。 2. 倉庫管理模組規範化: - 統一庫存分頁的視覺色調,使用一致的青色 (Cyan) 強調色。 - 優化日期篩選器,支援精確時間 (H:i) 選擇並預設 00:00。 - 實作自定義 multi-select 組件以解決 Preline 下拉衝突。 - 確保所有篩選操作觸發立即的 AJAX 資料同步與狀態更新。 3. 系統基礎優化: - 更新繁體中文、日文、英文語系檔,確保標籤顯示正確。 - 清理 Controller 多餘的地圖資料擷取邏輯。
This commit is contained in:
parent
39a246d3c5
commit
b0e523067f
@ -47,6 +47,17 @@ class MachineSettingController extends AdminController
|
||||
$machines = $machineQuery->latest()->paginate($per_page)->withQueryString();
|
||||
break;
|
||||
|
||||
case 'system_settings':
|
||||
$machineQuery = Machine::query()->with(['company']);
|
||||
if ($search) {
|
||||
$machineQuery->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('serial_no', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
$machines = $machineQuery->latest()->paginate($per_page)->withQueryString();
|
||||
break;
|
||||
|
||||
case 'models':
|
||||
$modelQuery = MachineModel::query()->withCount('machines');
|
||||
if ($search) {
|
||||
@ -70,6 +81,7 @@ class MachineSettingController extends AdminController
|
||||
}
|
||||
$users_list = $userQuery->latest()->paginate($per_page)->withQueryString();
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
$companies = \App\Models\System\Company::select('id', 'name', 'code')->get();
|
||||
@ -77,6 +89,7 @@ class MachineSettingController extends AdminController
|
||||
$tabView = match($tab) {
|
||||
'models' => 'admin.basic-settings.machines.partials.tab-models',
|
||||
'permissions' => 'admin.basic-settings.machines.partials.tab-permissions',
|
||||
'system_settings' => 'admin.basic-settings.machines.partials.tab-system-settings',
|
||||
default => 'admin.basic-settings.machines.partials.tab-machines',
|
||||
};
|
||||
return response()->view($tabView, compact(
|
||||
@ -194,6 +207,12 @@ class MachineSettingController extends AdminController
|
||||
'is_spring_slot_41_50' => 'boolean',
|
||||
'is_spring_slot_51_60' => 'boolean',
|
||||
'member_system_enabled' => 'boolean',
|
||||
'tax_invoice_enabled' => 'boolean',
|
||||
'card_terminal_enabled' => 'boolean',
|
||||
'scan_pay_esun_enabled' => 'boolean',
|
||||
'scan_pay_linepay_enabled' => 'boolean',
|
||||
'shopping_cart_enabled' => 'boolean',
|
||||
'cash_module_enabled' => 'boolean',
|
||||
'machine_model_id' => 'required|exists:machine_models,id',
|
||||
'payment_config_id' => 'nullable|exists:payment_configs,id',
|
||||
'location' => 'nullable|string|max:255',
|
||||
@ -287,6 +306,62 @@ class MachineSettingController extends AdminController
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateSystemSettings(Request $request, Machine $machine): \Illuminate\Http\JsonResponse|RedirectResponse
|
||||
{
|
||||
\Log::info('Update System Settings Request:', $request->all());
|
||||
if ($request->has('settings')) {
|
||||
$settings = $request->input('settings');
|
||||
$allowedFields = [
|
||||
'tax_invoice_enabled',
|
||||
'card_terminal_enabled',
|
||||
'scan_pay_esun_enabled',
|
||||
'scan_pay_linepay_enabled',
|
||||
'shopping_cart_enabled',
|
||||
'welcome_gift_enabled',
|
||||
'cash_module_enabled',
|
||||
'member_system_enabled',
|
||||
];
|
||||
$data = [];
|
||||
foreach ($allowedFields as $field) {
|
||||
$data[$field] = isset($settings[$field]) ? (bool)$settings[$field] : false;
|
||||
}
|
||||
$machine->update(array_merge($data, ['updater_id' => auth()->id()]));
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => __('Settings updated successfully.')
|
||||
]);
|
||||
}
|
||||
return redirect()->back()->with('success', __('Settings updated successfully.'));
|
||||
}
|
||||
|
||||
$field = $request->input('field');
|
||||
$value = $request->boolean('value');
|
||||
|
||||
$allowedFields = [
|
||||
'tax_invoice_enabled',
|
||||
'card_terminal_enabled',
|
||||
'scan_pay_esun_enabled',
|
||||
'scan_pay_linepay_enabled',
|
||||
'shopping_cart_enabled',
|
||||
'welcome_gift_enabled',
|
||||
'cash_module_enabled',
|
||||
'member_system_enabled',
|
||||
];
|
||||
|
||||
if (!in_array($field, $allowedFields)) {
|
||||
return response()->json(['success' => false, 'message' => 'Invalid field'], 400);
|
||||
}
|
||||
|
||||
$machine->update([$field => $value, 'updater_id' => auth()->id()]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => __('Setting updated successfully.')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 公開機台分布地圖
|
||||
*/
|
||||
|
||||
@ -74,13 +74,6 @@ class CompanyController extends Controller
|
||||
if (isset($validated['settings'])) {
|
||||
$validated['settings']['enable_material_code'] = filter_var($validated['settings']['enable_material_code'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['enable_points'] = filter_var($validated['settings']['enable_points'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['tax_invoice'] = filter_var($validated['settings']['tax_invoice'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['card_terminal'] = filter_var($validated['settings']['card_terminal'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['scan_pay_esun'] = filter_var($validated['settings']['scan_pay_esun'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['scan_pay_linepay'] = filter_var($validated['settings']['scan_pay_linepay'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['shopping_cart'] = filter_var($validated['settings']['shopping_cart'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['welcome_gift'] = filter_var($validated['settings']['welcome_gift'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['cash_module'] = filter_var($validated['settings']['cash_module'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($validated) {
|
||||
@ -185,13 +178,6 @@ class CompanyController extends Controller
|
||||
if (isset($validated['settings'])) {
|
||||
$validated['settings']['enable_material_code'] = filter_var($validated['settings']['enable_material_code'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['enable_points'] = filter_var($validated['settings']['enable_points'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['tax_invoice'] = filter_var($validated['settings']['tax_invoice'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['card_terminal'] = filter_var($validated['settings']['card_terminal'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['scan_pay_esun'] = filter_var($validated['settings']['scan_pay_esun'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['scan_pay_linepay'] = filter_var($validated['settings']['scan_pay_linepay'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['shopping_cart'] = filter_var($validated['settings']['shopping_cart'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['welcome_gift'] = filter_var($validated['settings']['welcome_gift'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['cash_module'] = filter_var($validated['settings']['cash_module'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($validated, $company) {
|
||||
@ -230,13 +216,6 @@ class CompanyController extends Controller
|
||||
$formattedSettings = [
|
||||
'enable_material_code' => filter_var($settings['enable_material_code'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'enable_points' => filter_var($settings['enable_points'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'tax_invoice' => filter_var($settings['tax_invoice'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'card_terminal' => filter_var($settings['card_terminal'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'scan_pay_esun' => filter_var($settings['scan_pay_esun'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'scan_pay_linepay' => filter_var($settings['scan_pay_linepay'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'shopping_cart' => filter_var($settings['shopping_cart'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'welcome_gift' => filter_var($settings['welcome_gift'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'cash_module' => filter_var($settings['cash_module'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
];
|
||||
|
||||
// Ensure we force save the JSON cast properly
|
||||
|
||||
@ -147,20 +147,26 @@ class WarehouseController extends Controller
|
||||
|
||||
public function inventory(Request $request)
|
||||
{
|
||||
$tab = $request->input('tab', 'stock-in');
|
||||
$tab = $request->input('tab', 'stock');
|
||||
$warehouses = Warehouse::active()->orderBy('name')->get();
|
||||
$isAjax = $request->ajax();
|
||||
|
||||
$data = compact('warehouses', 'tab');
|
||||
|
||||
if ($tab === 'stock') {
|
||||
$inventory_warehouses = Warehouse::active()->orderBy('name')->get(['id', 'name', 'type']);
|
||||
// 1. Current Stocks (stock)
|
||||
if (!$isAjax || $tab === 'stock') {
|
||||
$warehouse_ids = $request->input('warehouse_ids');
|
||||
$inventory_warehouses = Warehouse::active()
|
||||
->when($warehouse_ids, fn($q) => $q->whereIn('id', (array)$warehouse_ids))
|
||||
->orderBy('name')
|
||||
->get(['id', 'name', 'type']);
|
||||
|
||||
$query = Product::with(['stocks' => function($q) {
|
||||
$q->select('id', 'product_id', 'warehouse_id', 'quantity', 'safety_stock');
|
||||
}, 'translations']);
|
||||
|
||||
if ($request->filled('warehouse_id')) {
|
||||
$query->whereHas('stocks', fn($q) => $q->where('warehouse_id', $request->input('warehouse_id')));
|
||||
if ($warehouse_ids) {
|
||||
$query->whereHas('stocks', fn($q) => $q->whereIn('warehouse_id', (array)$warehouse_ids));
|
||||
}
|
||||
if ($search = $request->input('search')) {
|
||||
$query->where('name', 'like', "%{$search}%");
|
||||
@ -168,20 +174,30 @@ class WarehouseController extends Controller
|
||||
|
||||
$data['inventory_warehouses'] = $inventory_warehouses;
|
||||
$data['products'] = $query->orderBy('name')
|
||||
->paginate($request->input('per_page', 15))
|
||||
->paginate($request->input('per_page', 15), ['*'], 'stock_page')
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
} elseif ($tab === 'stock-in') {
|
||||
// 2. Stock-In Orders (stock-in)
|
||||
if (!$isAjax || $tab === 'stock-in') {
|
||||
$query = StockInOrder::with(['warehouse:id,name', 'creator:id,name'])
|
||||
->latest();
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->input('status'));
|
||||
}
|
||||
$data['orders'] = $query->paginate($request->input('per_page', 10))->withQueryString();
|
||||
$data['products'] = Product::orderBy('name')->get(['id', 'name', 'image_url']);
|
||||
if ($search = $request->input('search_order')) {
|
||||
$query->where('order_no', 'like', "%{$search}%");
|
||||
}
|
||||
if ($request->filled('start_date') && $request->filled('end_date')) {
|
||||
$query->whereBetween('created_at', [$request->start_date, $request->end_date]);
|
||||
}
|
||||
$data['orders'] = $query->paginate($request->input('per_page', 10), ['*'], 'order_page')->withQueryString();
|
||||
$data['stock_in_products'] = Product::orderBy('name')->get(['id', 'name', 'image_url']);
|
||||
}
|
||||
|
||||
} elseif ($tab === 'movements') {
|
||||
// 3. Movement History (movements)
|
||||
if (!$isAjax || $tab === 'movements') {
|
||||
$query = StockMovement::with(['warehouse:id,name', 'product:id,name', 'creator:id,name'])
|
||||
->orderBy('created_at', 'desc');
|
||||
|
||||
@ -191,10 +207,13 @@ class WarehouseController extends Controller
|
||||
if ($request->filled('type')) {
|
||||
$query->where('type', $request->input('type'));
|
||||
}
|
||||
$data['movements'] = $query->paginate($request->input('per_page', 15))->withQueryString();
|
||||
if ($request->filled('start_date') && $request->filled('end_date')) {
|
||||
$query->whereBetween('created_at', [$request->start_date, $request->end_date]);
|
||||
}
|
||||
$data['movements'] = $query->paginate($request->input('per_page', 15), ['*'], 'movement_page')->withQueryString();
|
||||
}
|
||||
|
||||
if ($request->ajax()) {
|
||||
if ($isAjax) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'tab' => $tab,
|
||||
|
||||
@ -85,6 +85,12 @@ class Machine extends Model
|
||||
'images',
|
||||
'creator_id',
|
||||
'updater_id',
|
||||
'tax_invoice_enabled',
|
||||
'card_terminal_enabled',
|
||||
'scan_pay_esun_enabled',
|
||||
'scan_pay_linepay_enabled',
|
||||
'shopping_cart_enabled',
|
||||
'cash_module_enabled',
|
||||
];
|
||||
|
||||
protected $appends = ['image_urls', 'calculated_status'];
|
||||
@ -177,6 +183,12 @@ class Machine extends Model
|
||||
'is_spring_slot_41_50' => 'boolean',
|
||||
'is_spring_slot_51_60' => 'boolean',
|
||||
'member_system_enabled' => 'boolean',
|
||||
'tax_invoice_enabled' => 'boolean',
|
||||
'card_terminal_enabled' => 'boolean',
|
||||
'scan_pay_esun_enabled' => 'boolean',
|
||||
'scan_pay_linepay_enabled' => 'boolean',
|
||||
'shopping_cart_enabled' => 'boolean',
|
||||
'cash_module_enabled' => 'boolean',
|
||||
'images' => 'array',
|
||||
];
|
||||
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
<?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('machines', function (Blueprint $table) {
|
||||
$table->boolean('tax_invoice_enabled')->default(0)->after('invoice_status')->comment('電子發票開關');
|
||||
$table->boolean('card_terminal_enabled')->default(0)->after('card_reader_no')->comment('刷卡機系統開關');
|
||||
$table->boolean('scan_pay_esun_enabled')->default(0)->after('card_terminal_enabled')->comment('玉山掃碼支付開關');
|
||||
$table->boolean('scan_pay_linepay_enabled')->default(0)->after('scan_pay_esun_enabled')->comment('LinePay支付開關');
|
||||
$table->boolean('shopping_cart_enabled')->default(0)->after('scan_pay_linepay_enabled')->comment('購物車功能開關');
|
||||
$table->boolean('welcome_gift_enabled')->default(0)->after('shopping_cart_enabled')->comment('迎賓禮功能開關');
|
||||
$table->boolean('cash_module_enabled')->default(0)->after('welcome_gift_enabled')->comment('現金模組開關');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('machines', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'tax_invoice_enabled',
|
||||
'card_terminal_enabled',
|
||||
'scan_pay_esun_enabled',
|
||||
'scan_pay_linepay_enabled',
|
||||
'shopping_cart_enabled',
|
||||
'welcome_gift_enabled',
|
||||
'cash_module_enabled',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -194,6 +194,7 @@
|
||||
"Click to Open Dashboard": "Click to Open Dashboard",
|
||||
"Click to upload": "Click to upload",
|
||||
"Close Panel": "Close Panel",
|
||||
"Coin\/Banknote Machine": "Coin\/Banknote Machine",
|
||||
"Coin\/Bill Acceptor": "Coin\/Bill Acceptor",
|
||||
"Command Center": "Command Center",
|
||||
"Command Confirmation": "Command Confirmation",
|
||||
@ -264,6 +265,7 @@
|
||||
"Created By": "Created By",
|
||||
"Creation Time": "Creation Time",
|
||||
"Creator": "Creator",
|
||||
"Credit Card": "Credit Card",
|
||||
"Credit Card \/ Contactless": "Credit Card \/ Contactless",
|
||||
"Critical": "Critical",
|
||||
"Current": "Current",
|
||||
@ -334,6 +336,8 @@
|
||||
"Draft": "Draft",
|
||||
"Duration": "Duration",
|
||||
"Duration (Seconds)": "Duration (Seconds)",
|
||||
"E.SUN Bank Scan": "E.SUN Bank Scan",
|
||||
"E.SUN Pay": "E.SUN Pay",
|
||||
"E.SUN QR Pay": "E.SUN QR Pay",
|
||||
"E.SUN QR Scan": "E.SUN QR Scan",
|
||||
"E.SUN QR Scan Settings Description": "E.SUN Bank QR Scan Payment Settings",
|
||||
@ -703,6 +707,7 @@
|
||||
"No products found matching your criteria.": "No products found matching your criteria.",
|
||||
"No records found": "No records found",
|
||||
"No replenishment orders found": "No replenishment orders found",
|
||||
"No results found": "No results found",
|
||||
"No roles available": "No roles available",
|
||||
"No roles found.": "No roles found.",
|
||||
"No slot data": "No slot data",
|
||||
@ -1027,6 +1032,7 @@
|
||||
"Serial Number": "Serial Number",
|
||||
"Service Periods": "Service Periods",
|
||||
"Service Terms": "Service Periods",
|
||||
"Settings updated successfully.": "Settings updated successfully.",
|
||||
"Settlement": "Settlement",
|
||||
"Shopping Cart": "Shopping Cart",
|
||||
"Shopping Cart Feature": "Shopping Cart Feature",
|
||||
@ -1213,6 +1219,7 @@
|
||||
"Update Product": "Update Product",
|
||||
"Update Settings": "Update Settings",
|
||||
"Update existing role and permissions.": "Update existing role and permissions.",
|
||||
"Update failed": "Update failed",
|
||||
"Update identification for your asset": "Update identification for your asset",
|
||||
"Update your account's profile information and email address.": "Update your account's profile information and email address.",
|
||||
"Upload Image": "Upload Image",
|
||||
|
||||
16
lang/ja.json
16
lang/ja.json
@ -18,6 +18,7 @@
|
||||
"Card Terminal System": "カード端末システム",
|
||||
"Cash Module": "現金決済モジュール",
|
||||
"Cash Module Feature": "現金モジュール機能",
|
||||
"Coin\/Banknote Machine": "硬貨\/紙幣モジュール",
|
||||
"Coin\/Bill Acceptor": "コイン\/紙幣機",
|
||||
"Completed": "完了",
|
||||
"Confirm": "確認",
|
||||
@ -31,10 +32,13 @@
|
||||
"Create stock transfers between warehouses and machine returns": "倉庫間の転送および機台からの返品伝票を作成",
|
||||
"Created At": "作成日時",
|
||||
"Created By": "作成者",
|
||||
"Credit Card": "クレジットカード",
|
||||
"Damage": "破損",
|
||||
"Delivering": "配送中",
|
||||
"Display Material Code": "素材コードを表示",
|
||||
"Draft": "下書き",
|
||||
"E.SUN Bank Scan": "玉山銀行スキャン",
|
||||
"E.SUN Pay": "E.SUN Pay",
|
||||
"E.SUN QR Pay": "玉山QR決済",
|
||||
"ESUN": "玉山銀行 (ESUN)",
|
||||
"Electronic Invoice": "電子翻訳",
|
||||
@ -52,10 +56,12 @@
|
||||
"Includes Credit Card\/Mobile Pay": "クレジットカード\/モバイル決済を含む",
|
||||
"Inventory Management": "在庫管理",
|
||||
"LinePay": "LinePay",
|
||||
"LinePay Payment": "LinePay決済",
|
||||
"LinePay Payment": "LinePay 決済",
|
||||
"Loading...": "読み込み中...",
|
||||
"Low Stock": "在庫不足",
|
||||
"Low Stock Alerts": "低在庫アラート",
|
||||
"Machine Distribution": "機器分布",
|
||||
"Machine Distribution Map": "機器分布マップ",
|
||||
"Machine Inventory Overview": "機台在庫概要",
|
||||
"Machine Replenishment": "機台補充",
|
||||
"Machine Return": "機台返品",
|
||||
@ -113,6 +119,7 @@
|
||||
"Select Product": "商品を選択",
|
||||
"Select Warehouse": "倉庫を選択",
|
||||
"Serial No.": "シリアル番号",
|
||||
"Settings updated successfully.": "設定が正常に更新されました。",
|
||||
"Shopping Cart": "ショッピングカート",
|
||||
"Shopping Cart Feature": "ショッピングカート機能",
|
||||
"Slot": "貨道",
|
||||
@ -145,6 +152,7 @@
|
||||
"Transfers": "転送",
|
||||
"Unpublish": "配信終了",
|
||||
"Update Settings": "設定を更新",
|
||||
"Update failed": "更新に失敗しました",
|
||||
"Video": "動画",
|
||||
"View Details": "詳細を表示",
|
||||
"View Inventory": "在庫を表示",
|
||||
@ -161,5 +169,9 @@
|
||||
"Warehouse to Warehouse": "倉庫間転送",
|
||||
"Warehouse updated successfully.": "倉庫が正常に更新されました。",
|
||||
"Welcome Gift": "登録特典",
|
||||
"Welcome Gift Feature": "来店特典機能"
|
||||
"Welcome Gift Feature": "来店特典機能",
|
||||
"Visual overview of all machine locations": "全機器位置のビジュアル概要",
|
||||
"Online": "オンライン",
|
||||
"Offline": "オフライン",
|
||||
"Error": "エラー"
|
||||
}
|
||||
@ -165,6 +165,7 @@
|
||||
"Cannot delete model that is currently in use by machines.": "無法刪除目前正在被機台使用的型號。",
|
||||
"Cannot delete role with active users.": "無法刪除已有綁定帳號的角色。",
|
||||
"Cannot delete warehouse with existing stock": "無法刪除仍有庫存的倉庫",
|
||||
"Card Machine System": "刷卡機系統",
|
||||
"Card Reader": "刷卡機",
|
||||
"Card Reader No": "刷卡機編號",
|
||||
"Card Reader Reboot": "刷卡機重啟",
|
||||
@ -175,6 +176,7 @@
|
||||
"Card Terminal System": "刷卡機系統",
|
||||
"Cash Module": "硬幣機 \/ 紙鈔機模組",
|
||||
"Cash Module Feature": "現金模組功能",
|
||||
"Cash Module Function": "現金模組功能",
|
||||
"Category": "類別",
|
||||
"Category Management": "分類管理",
|
||||
"Category Name": "分類名稱",
|
||||
@ -198,6 +200,7 @@
|
||||
"Click to Open Dashboard": "點擊開啟儀表板",
|
||||
"Click to upload": "點擊上傳",
|
||||
"Close Panel": "關閉面板",
|
||||
"Coin\/Banknote Machine": "硬幣機\/紙鈔機",
|
||||
"Coin\/Bill Acceptor": "硬幣機\/紙鈔機",
|
||||
"Command Center": "指令中心",
|
||||
"Command Confirmation": "指令確認",
|
||||
@ -229,7 +232,6 @@
|
||||
"Confirm replenishment completed?": "確定補貨已完成?",
|
||||
"Confirm this stock-in order?": "確定要確認此進貨單嗎?",
|
||||
"Confirm this transfer?": "確定要確認此調撥作業嗎?",
|
||||
"Current Stocks": "目前庫存",
|
||||
"Connected": "通訊中",
|
||||
"Connecting...": "連線中",
|
||||
"Connection lost (LWT)": "連線中斷",
|
||||
@ -273,12 +275,14 @@
|
||||
"Created By": "建立者",
|
||||
"Creation Time": "建立時間",
|
||||
"Creator": "建立者",
|
||||
"Credit Card": "信用卡",
|
||||
"Credit Card \/ Contactless": "支援信用卡 \/ 感應支付",
|
||||
"Critical": "嚴重",
|
||||
"Current": "當前",
|
||||
"Current Password": "當前密碼",
|
||||
"Current Status": "當前狀態",
|
||||
"Current Stock": "當前庫存",
|
||||
"Current Stocks": "目前庫存",
|
||||
"Current Type": "當前類型",
|
||||
"Current:": "現:",
|
||||
"Customer Details": "客戶詳情",
|
||||
@ -350,7 +354,9 @@
|
||||
"Draft": "草稿",
|
||||
"Duration": "時長",
|
||||
"Duration (Seconds)": "播放秒數",
|
||||
"E.SUN QR Pay": "玉山掃碼支付",
|
||||
"E.SUN Bank Scan": "玉山銀行掃碼",
|
||||
"E.SUN Pay": "玉山 Pay",
|
||||
"E.SUN QR Pay": "玉山銀行掃碼",
|
||||
"E.SUN QR Scan": "玉山銀行標籤支付",
|
||||
"E.SUN QR Scan Settings Description": "玉山銀行掃碼支付設定",
|
||||
"EASY_MERCHANT_ID": "悠遊付 商店代號",
|
||||
@ -437,10 +443,11 @@
|
||||
"Failed to update machine images: ": "更新機台圖片失敗:",
|
||||
"Feature Settings": "功能設定",
|
||||
"Feature Toggles": "功能開關",
|
||||
"Filter": "篩選",
|
||||
"Fill Rate": "滿載率",
|
||||
"Fill in the device repair or maintenance details": "填寫設備維修或保養詳情",
|
||||
"Fill in the product details below": "請在下方填寫商品的詳細資訊",
|
||||
"Filter": "篩選",
|
||||
"Filter by Warehouse Presence": "依據倉庫存貨篩選",
|
||||
"Firmware Version": "韌體版本",
|
||||
"Firmware updated to :version": "韌體版本更新 : :version",
|
||||
"Fleet Avg OEE": "全機台平均 OEE",
|
||||
@ -454,6 +461,7 @@
|
||||
"Full Access": "全機台授權",
|
||||
"Full Name": "名稱",
|
||||
"Full Points": "全點數",
|
||||
"Functional Settings": "功能設定",
|
||||
"Games": "互動遊戲",
|
||||
"General permissions not linked to a specific menu.": "未連結到特定選單的一般權限。",
|
||||
"Gift Definitions": "禮品設定",
|
||||
@ -483,7 +491,7 @@
|
||||
"Image": "圖片",
|
||||
"Immediate": "立即",
|
||||
"In Stock": "當前庫存",
|
||||
"Includes Credit Card\/Mobile Pay": "包括 \/信用卡\/卡片支付\/手機支付",
|
||||
"Includes Credit Card\/Mobile Pay": "含信用卡\/行動支付",
|
||||
"Indefinite": "無限期",
|
||||
"Info": "一般",
|
||||
"Initial Admin Account": "初始管理帳號",
|
||||
@ -559,6 +567,7 @@
|
||||
"Machine Details": "機台詳情",
|
||||
"Machine Distribution": "機台分布",
|
||||
"Machine Distribution Map": "機台分布地圖",
|
||||
"Visual overview of all machine locations": "所有機台位置的視覺化概覽",
|
||||
"Machine Images": "機台照片",
|
||||
"Machine Info": "機台資訊",
|
||||
"Machine Information": "機台資訊",
|
||||
@ -583,6 +592,7 @@
|
||||
"Machine Settings": "機台設定",
|
||||
"Machine Status": "機台狀態",
|
||||
"Machine Status List": "機台運行狀態列表",
|
||||
"Machine System Settings": "機台系統設定",
|
||||
"Machine Utilization": "機台稼動率",
|
||||
"Machine created successfully.": "機台已成功建立。",
|
||||
"Machine images updated successfully.": "機台圖片已成功更新。",
|
||||
@ -725,6 +735,7 @@
|
||||
"No products found matching your criteria.": "找不到符合條件的商品。",
|
||||
"No records found": "未找到相關紀錄",
|
||||
"No replenishment orders found": "查無補貨單紀錄",
|
||||
"No results found": "查無搜尋結果",
|
||||
"No roles available": "目前沒有角色資料。",
|
||||
"No roles found.": "找不到角色資料。",
|
||||
"No slot data": "找不到貨道資料",
|
||||
@ -871,7 +882,7 @@
|
||||
"Price \/ Member": "售價 \/ 會員價",
|
||||
"Pricing Information": "價格資訊",
|
||||
"Product": "商品",
|
||||
"Product / Stock": "商品 / 庫存",
|
||||
"Product \/ Stock": "商品 \/ 庫存",
|
||||
"Product Count": "商品數量",
|
||||
"Product Details": "商品細項",
|
||||
"Product ID": "商品編號",
|
||||
@ -1000,7 +1011,7 @@
|
||||
"Saved.": "已儲存",
|
||||
"Saving...": "儲存中...",
|
||||
"Scale level and access control": "層級與存取控制",
|
||||
"Scan Pay": "掃碼支付",
|
||||
"Scan Pay": "掃碼支付功能",
|
||||
"Scan this code to quickly access the maintenance form for this device.": "掃描此 QR Code 即可快速進入此設備的維修單填寫頁面。",
|
||||
"Schedule": "排程區間",
|
||||
"Search Company Title...": "搜尋公司名稱...",
|
||||
@ -1062,9 +1073,11 @@
|
||||
"Service Periods": "服務區間",
|
||||
"Service Terms": "服務期程",
|
||||
"Settings Saved": "設定已儲存",
|
||||
"Settings updated successfully.": "設定更新成功。",
|
||||
"Settlement": "結帳處理",
|
||||
"Shopping Cart": "購物車功能",
|
||||
"Shopping Cart": "購物車",
|
||||
"Shopping Cart Feature": "購物車功能",
|
||||
"Shopping Cart Function": "購物車功能",
|
||||
"Show": "顯示",
|
||||
"Show material code field in products": "在商品資料中顯示物料編號欄位",
|
||||
"Show points rules in products": "在商品資料中顯示點數規則相關欄位",
|
||||
@ -1259,6 +1272,7 @@
|
||||
"Update Product": "更新商品",
|
||||
"Update Settings": "更新設定",
|
||||
"Update existing role and permissions.": "更新現有角色與權限設定。",
|
||||
"Update failed": "更新失敗",
|
||||
"Update identification for your asset": "更新您的資產識別名稱",
|
||||
"Update your account's profile information and email address.": "更新您的帳號名稱、手機號碼與電子郵件地址。",
|
||||
"Upload Image": "上傳圖片",
|
||||
@ -1320,10 +1334,9 @@
|
||||
"Warranty Service": "保固服務",
|
||||
"Warranty Start": "保固起始",
|
||||
"Welcome Gift": "來店禮",
|
||||
"Welcome Gift Feature": "來店禮功能",
|
||||
"Welcome Gift Feature": "迎賓禮功能",
|
||||
"Welcome Gift Function": "迎賓禮功能",
|
||||
"Welcome Gift Status": "來店禮",
|
||||
"Work Content": "工作內容",
|
||||
"Yes, regenerate": "確認重新產生",
|
||||
"Yesterday": "昨日",
|
||||
"You cannot assign permissions you do not possess.": "您無法指派您自身不具備的權限。",
|
||||
"You cannot delete your own account.": "您無法刪除自己的帳號。",
|
||||
@ -1422,6 +1435,5 @@
|
||||
"visit_gift": "來店禮",
|
||||
"vs Yesterday": "較昨日",
|
||||
"warehouses": "倉庫管理",
|
||||
"Filter by Warehouse Presence": "依據倉庫存貨篩選",
|
||||
"待填寫": "待填寫"
|
||||
}
|
||||
@ -8,6 +8,61 @@
|
||||
modelSearch: '',
|
||||
permissionSearch: '',
|
||||
permissionCompanyId: '{{ request('company_id') }}',
|
||||
isUpdatingSetting: false,
|
||||
async toggleSystemSetting(machineId, field, value) {
|
||||
if (this.isUpdatingSetting) return;
|
||||
this.isUpdatingSetting = true;
|
||||
try {
|
||||
const res = await fetch(`/admin/basic-settings/machines/${machineId}/update-system-settings`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name=\'csrf-token\']').content,
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ field, value })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
window.dispatchEvent(new CustomEvent('toast', { detail: { message: data.message, type: 'success' } }));
|
||||
} else {
|
||||
throw new Error(data.message || 'Update failed');
|
||||
}
|
||||
} catch (e) {
|
||||
window.dispatchEvent(new CustomEvent('toast', { detail: { message: e.message, type: 'error' } }));
|
||||
// Refresh content to reset toggle state on UI
|
||||
this.searchInTab('system_settings');
|
||||
} finally {
|
||||
this.isUpdatingSetting = false;
|
||||
}
|
||||
},
|
||||
async submitMachineSettings() {
|
||||
if (this.isUpdatingSetting || !this.currentMachine) return;
|
||||
this.isUpdatingSetting = true;
|
||||
try {
|
||||
const res = await fetch(`/admin/basic-settings/machines/${this.currentMachine.id}/update-system-settings`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name=\'csrf-token\']').content,
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ settings: this.machineSettings })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
window.dispatchEvent(new CustomEvent('toast', { detail: { message: data.message, type: 'success' } }));
|
||||
this.showMachineSettingsModal = false;
|
||||
this.searchInTab('system_settings');
|
||||
} else {
|
||||
throw new Error(data.message || '{{ __('Update failed') }}');
|
||||
}
|
||||
} catch (e) {
|
||||
window.dispatchEvent(new CustomEvent('toast', { detail: { message: e.message, type: 'error' } }));
|
||||
} finally {
|
||||
this.isUpdatingSetting = false;
|
||||
}
|
||||
},
|
||||
showCreateMachineModal: false,
|
||||
showPhotoModal: false,
|
||||
showDetailDrawer: false,
|
||||
@ -25,6 +80,22 @@
|
||||
maintenanceQrMachineName: '',
|
||||
maintenanceQrUrl: '',
|
||||
permissionSearchQuery: '',
|
||||
showMachineSettingsModal: false,
|
||||
machineSettings: {},
|
||||
openMachineSettingsModal(machine) {
|
||||
this.currentMachine = machine;
|
||||
this.machineSettings = {
|
||||
tax_invoice_enabled: machine.tax_invoice_enabled === true || machine.tax_invoice_enabled === 1 || machine.tax_invoice_enabled === '1',
|
||||
card_terminal_enabled: machine.card_terminal_enabled === true || machine.card_terminal_enabled === 1 || machine.card_terminal_enabled === '1',
|
||||
scan_pay_esun_enabled: machine.scan_pay_esun_enabled === true || machine.scan_pay_esun_enabled === 1 || machine.scan_pay_esun_enabled === '1',
|
||||
scan_pay_linepay_enabled: machine.scan_pay_linepay_enabled === true || machine.scan_pay_linepay_enabled === 1 || machine.scan_pay_linepay_enabled === '1',
|
||||
shopping_cart_enabled: machine.shopping_cart_enabled === true || machine.shopping_cart_enabled === 1 || machine.shopping_cart_enabled === '1',
|
||||
welcome_gift_enabled: machine.welcome_gift_enabled === true || machine.welcome_gift_enabled === 1 || machine.welcome_gift_enabled === '1',
|
||||
cash_module_enabled: machine.cash_module_enabled === true || machine.cash_module_enabled === 1 || machine.cash_module_enabled === '1',
|
||||
member_system_enabled: machine.member_system_enabled === true || machine.member_system_enabled === 1 || machine.member_system_enabled === '1'
|
||||
};
|
||||
this.showMachineSettingsModal = true;
|
||||
},
|
||||
openMaintenanceQr(machine) {
|
||||
this.maintenanceQrMachineName = machine.name;
|
||||
const baseUrl = '{{ route('admin.maintenance.create', ['serial_no' => 'SERIAL_NO']) }}';
|
||||
@ -197,7 +268,7 @@
|
||||
// === 搜尋/分頁 AJAX(僅在搜尋或換頁時觸發,Tab 切換不走此路) ===
|
||||
async searchInTab(tabName, extraQuery = '') {
|
||||
this.tabLoading = true;
|
||||
const searchMap = { machines: this.machineSearch, models: this.modelSearch, permissions: this.permissionSearch };
|
||||
const searchMap = { machines: this.machineSearch, models: this.modelSearch, permissions: this.permissionSearch, system_settings: this.machineSearch, distribution: '' };
|
||||
const search = searchMap[tabName] || '';
|
||||
let qs = `tab=${tabName}&_ajax=1`;
|
||||
if (search) qs += `&search=${encodeURIComponent(search)}`;
|
||||
@ -286,7 +357,7 @@
|
||||
});
|
||||
// 首次載入時綁定每個 Tab 的分頁連結
|
||||
this.$nextTick(() => {
|
||||
['machines', 'models', 'permissions'].forEach(t => {
|
||||
['machines', 'models', 'permissions', 'system_settings'].forEach(t => {
|
||||
const ref = this.$refs[t + 'Content'];
|
||||
if (ref) this.bindPaginationLinks(ref, t);
|
||||
});
|
||||
@ -294,6 +365,7 @@
|
||||
// Tab 切換時同步 URL
|
||||
this.$watch('tab', (newTab) => {
|
||||
history.pushState({}, '', `{{ route('admin.basic-settings.machines.index') }}?tab=${newTab}`);
|
||||
window.dispatchEvent(new CustomEvent('tab-changed', { detail: { tab: newTab } }));
|
||||
});
|
||||
// 瀏覽器上一頁/下一頁
|
||||
window.addEventListener('popstate', () => {
|
||||
@ -302,19 +374,209 @@
|
||||
});
|
||||
}
|
||||
}" @execute-regenerate.window="executeRegeneration($event.detail)">
|
||||
|
||||
<!-- Machine System Settings Modal -->
|
||||
<template x-teleport="body">
|
||||
<div x-show="showMachineSettingsModal" class="fixed inset-0 z-[200] flex items-center justify-center" x-cloak>
|
||||
<div x-show="showMachineSettingsModal"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="fixed inset-0 bg-slate-900/40 backdrop-blur-sm" @click="showMachineSettingsModal = false"></div>
|
||||
|
||||
<div x-show="showMachineSettingsModal"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
class="relative bg-white dark:bg-slate-900 rounded-3xl shadow-2xl border border-slate-100 dark:border-slate-800 w-full max-w-2xl mx-4 overflow-hidden z-[201] flex flex-col max-h-[90vh]">
|
||||
|
||||
<div class="px-8 py-6 border-b border-slate-100 dark:border-slate-800 flex justify-between items-center bg-slate-50/50 dark:bg-slate-900/50">
|
||||
<div>
|
||||
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Edit Machine System Settings') }}</h3>
|
||||
<p class="text-xs font-bold text-slate-400 mt-1 uppercase tracking-widest" x-text="currentMachine?.name + ' (' + currentMachine?.serial_no + ')'"></p>
|
||||
</div>
|
||||
<button type="button" @click="showMachineSettingsModal = false" class="text-slate-400 hover:text-slate-500 bg-white dark:bg-slate-800 hover:bg-slate-100 dark:hover:bg-slate-700 rounded-xl p-2 transition-colors">
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-8 overflow-y-auto custom-scrollbar flex-1 bg-white dark:bg-slate-900">
|
||||
<form @submit.prevent="submitMachineSettings()">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- 稅務系統 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Tax System') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[tax_invoice_enabled]" value="0">
|
||||
<input type="checkbox" name="settings[tax_invoice_enabled]" value="1" x-model="machineSettings.tax_invoice_enabled" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('Electronic Invoice') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 刷卡機系統 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Card Machine System') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[card_terminal_enabled]" value="0">
|
||||
<input type="checkbox" name="settings[card_terminal_enabled]" value="1" x-model="machineSettings.card_terminal_enabled" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('Includes Credit Card/Mobile Pay') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 掃碼支付功能 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Scan Pay') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[scan_pay_esun_enabled]" value="0">
|
||||
<input type="checkbox" name="settings[scan_pay_esun_enabled]" value="1" x-model="machineSettings.scan_pay_esun_enabled" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('E.SUN QR Pay') }}</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[scan_pay_linepay_enabled]" value="0">
|
||||
<input type="checkbox" name="settings[scan_pay_linepay_enabled]" value="1" x-model="machineSettings.scan_pay_linepay_enabled" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('LinePay Payment') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 購物車功能 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('購物車功能') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[shopping_cart_enabled]" value="0">
|
||||
<input type="checkbox" name="settings[shopping_cart_enabled]" value="1" x-model="machineSettings.shopping_cart_enabled" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('購物車') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 來店禮功能 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('來店禮功能') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[welcome_gift_enabled]" value="0">
|
||||
<input type="checkbox" name="settings[welcome_gift_enabled]" value="1" x-model="machineSettings.welcome_gift_enabled" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('來店禮') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 現金模組功能 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('現金模組功能') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[cash_module_enabled]" value="0">
|
||||
<input type="checkbox" name="settings[cash_module_enabled]" value="1" x-model="machineSettings.cash_module_enabled" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('硬幣機/紙鈔機') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 會員系統 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('會員系統功能') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[member_system_enabled]" value="0">
|
||||
<input type="checkbox" name="settings[member_system_enabled]" value="1" x-model="machineSettings.member_system_enabled" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('會員系統') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-x-4 pt-8 mt-10 border-t border-slate-100 dark:border-slate-800">
|
||||
<button type="button" @click="showMachineSettingsModal = false" class="btn-luxury-ghost px-8">{{ __('Cancel') }}</button>
|
||||
<button type="submit" class="btn-luxury-primary px-12" :disabled="isUpdatingSetting">
|
||||
<span x-show="!isUpdatingSetting">{{ __('Update Settings') }}</span>
|
||||
<span x-show="isUpdatingSetting" class="flex items-center gap-2">
|
||||
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
{{ __('Updating...') }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 1. Header Area -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Machine Settings') }}</h1>
|
||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Management of operational parameters and models') }}</p>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-4">
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Machine Settings') }}</h1>
|
||||
<a href="{{ route('machines.distribution') }}" target="_blank" class="flex items-center gap-2 px-1 group transition-all pt-1">
|
||||
<svg class="w-5 h-5 text-slate-400 dark:text-white/40 group-hover:text-cyan-500 dark:group-hover:text-white transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 6.75V15m6-10.5v.106c0 .707-.555 1.296-1.244 1.333a5.232 5.232 0 0 0-4.834 4.834C8.889 11.418 8.299 12 7.591 12H7.5m0 0v7.5m0-7.5h.75m.75 0h.375c.49 0 .959.122 1.374.339a5.251 5.251 0 0 1 2.625 4.547v.105c0 .708.555 1.296 1.244 1.333a5.232 5.232 0 0 0 4.834-4.834c.038-.689.627-1.279 1.335-1.279h.75M15 6.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0ZM18.75 18.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Z" />
|
||||
</svg>
|
||||
<span class="text-base font-black text-slate-500 dark:text-white/60 group-hover:text-slate-700 dark:group-hover:text-white transition-colors">{{ __('Machine Distribution') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">{{ __('Management of operational parameters and models') }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="{{ route('machines.distribution') }}" target="_blank" class="btn-luxury-ghost flex items-center gap-2">
|
||||
<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="M9 6.75V15m6-10.5v.106c0 .707-.555 1.296-1.244 1.333a5.232 5.232 0 0 0-4.834 4.834C8.889 11.418 8.299 12 7.591 12H7.5m0 0v7.5m0-7.5h.75m.75 0h.375c.49 0 .959.122 1.374.339a5.251 5.251 0 0 1 2.625 4.547v.105c0 .708.555 1.296 1.244 1.333a5.232 5.232 0 0 0 4.834-4.834c.038-.689.627-1.279 1.335-1.279h.75M15 6.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0ZM18.75 18.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Z" />
|
||||
</svg>
|
||||
<span>{{ __('Machine Distribution') }}</span>
|
||||
</a>
|
||||
|
||||
<button x-show="tab === 'machines'" x-cloak @click="showCreateMachineModal = true" class="btn-luxury-primary flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
@ -347,6 +609,11 @@
|
||||
class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest transition-all">
|
||||
{{ __('Machine Permissions') }}
|
||||
</button>
|
||||
<button @click="tab = 'system_settings'"
|
||||
:class="tab === 'system_settings' ? '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">
|
||||
{{ __('Machine System Settings') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 2. Main Content Card -->
|
||||
@ -393,6 +660,12 @@
|
||||
@include('admin.basic-settings.machines.partials.tab-permissions')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div x-show="tab === 'system_settings'" x-cloak>
|
||||
<div x-ref="system_settingsContent">
|
||||
@include('admin.basic-settings.machines.partials.tab-system-settings')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -0,0 +1,152 @@
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div>
|
||||
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Machine Distribution Map') }}</h3>
|
||||
<p class="text-xs font-bold text-slate-400 mt-1 uppercase tracking-widest">{{ __('Visual overview of all machine locations') }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-full bg-emerald-500"></span>
|
||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Online') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-full bg-slate-400"></span>
|
||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Offline') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-full bg-rose-500"></span>
|
||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Error') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative rounded-3xl overflow-hidden border border-slate-100 dark:border-slate-800 h-[600px] shadow-sm">
|
||||
<div id="tab-map" class="w-full h-full z-10"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Leaflet CSS & JS (Only load if not already present) -->
|
||||
@once
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||
@endonce
|
||||
|
||||
<style>
|
||||
.luxury-popup .leaflet-popup-content-wrapper {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(8px);
|
||||
border-radius: 16px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.dark .luxury-popup .leaflet-popup-content-wrapper {
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.luxury-popup .leaflet-popup-content {
|
||||
margin: 0;
|
||||
}
|
||||
.luxury-popup .leaflet-popup-tip {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
.dark .luxury-popup .leaflet-popup-tip {
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
}
|
||||
.custom-marker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.marker-dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||
}
|
||||
.marker-online { background-color: #10b981; }
|
||||
.marker-offline { background-color: #64748b; }
|
||||
.marker-error { background-color: #ef4444; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const initMap = () => {
|
||||
const mapEl = document.getElementById('tab-map');
|
||||
if (!mapEl || mapEl._leaflet_id) return;
|
||||
|
||||
const distributionMachines = @json($distributionMachines);
|
||||
const map = L.map('tab-map', {
|
||||
zoomControl: true
|
||||
}).setView([23.973875, 120.982024], 7); // Center of Taiwan
|
||||
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>',
|
||||
subdomains: 'abcd',
|
||||
maxZoom: 20
|
||||
}).addTo(map);
|
||||
|
||||
distributionMachines.forEach(machine => {
|
||||
if (machine.latitude && machine.longitude) {
|
||||
const statusClass = `marker-${machine.status}`;
|
||||
const icon = L.divIcon({
|
||||
className: 'custom-marker',
|
||||
html: `<div class="marker-dot ${statusClass}"></div>`,
|
||||
iconSize: [20, 20],
|
||||
iconAnchor: [10, 10]
|
||||
});
|
||||
|
||||
const marker = L.marker([machine.latitude, machine.longitude], { icon: icon }).addTo(map);
|
||||
|
||||
const popupContent = `
|
||||
<div class="p-4 min-w-[200px] bg-white dark:bg-slate-900">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="w-2 h-2 rounded-full ${machine.status === 'online' ? 'bg-emerald-500' : (machine.status === 'offline' ? 'bg-slate-400' : 'bg-rose-500')}"></div>
|
||||
<span class="text-sm font-black text-slate-800 dark:text-white font-display">${machine.name}</span>
|
||||
</div>
|
||||
<p class="text-[11px] font-bold text-slate-500 dark:text-slate-400 mb-3">${machine.address || machine.location || ''}</p>
|
||||
<div class="flex items-center justify-between border-t border-slate-100 dark:border-slate-800 pt-3">
|
||||
<span class="text-[9px] font-black text-slate-400 uppercase tracking-widest">${machine.serial_no}</span>
|
||||
<span class="text-[9px] font-bold text-cyan-600 dark:text-cyan-400 uppercase tracking-widest">${machine.status.toUpperCase()}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
marker.bindPopup(popupContent, {
|
||||
maxWidth: 300,
|
||||
className: 'luxury-popup'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle resize when tab becomes visible
|
||||
window.addEventListener('resize', () => {
|
||||
setTimeout(() => map.invalidateSize(), 100);
|
||||
});
|
||||
|
||||
// Intersection Observer to handle initial render when tab is switched
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
setTimeout(() => map.invalidateSize(), 200);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
observer.observe(mapEl);
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initMap);
|
||||
} else {
|
||||
initMap();
|
||||
}
|
||||
|
||||
// Also hook into Alpine tab changes
|
||||
window.addEventListener('tab-changed', (e) => {
|
||||
if (e.detail.tab === 'distribution') {
|
||||
setTimeout(initMap, 100);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@ -0,0 +1,127 @@
|
||||
<div class="space-y-0">
|
||||
<!-- 1. Header with Search and Filter -->
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-6">
|
||||
<div class="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto">
|
||||
<!-- Search Bar -->
|
||||
<div class="relative group w-full md:w-64">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||
<svg class="h-4 w-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" x-model="machineSearch" @input.debounce.500ms="searchInTab('system_settings')"
|
||||
placeholder="{{ __('搜尋機台名稱或代號...') }}"
|
||||
class="py-2.5 pl-12 pr-6 block w-full luxury-input">
|
||||
</div>
|
||||
|
||||
<!-- Company Filter -->
|
||||
<div class="w-full md:w-48">
|
||||
<x-searchable-select
|
||||
x-model="permissionCompanyId"
|
||||
@change="searchInTab('system_settings')"
|
||||
:placeholder="__('所有公司')">
|
||||
@foreach ($companies as $company)
|
||||
<option value="{{ $company->id }}">{{ $company->name }}</option>
|
||||
@endforeach
|
||||
</x-searchable-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. Table Content -->
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||
<thead>
|
||||
<tr class="bg-slate-50/50 dark:bg-slate-900/30">
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800">
|
||||
{{ __('Machine Information') }}
|
||||
</th>
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800">
|
||||
{{ __('Company') }}
|
||||
</th>
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800">
|
||||
{{ __('Enabled Features') }}
|
||||
</th>
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-right">
|
||||
{{ __('Actions') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/50">
|
||||
@forelse($machines as $machine)
|
||||
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
||||
<td class="px-6 py-6 w-1/4">
|
||||
<div class="flex items-center gap-x-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 group-hover:bg-cyan-500 group-hover:text-white transition-all">
|
||||
<svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
<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">{{ $machine->name }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mt-0.5">
|
||||
<span class="text-xs font-mono font-bold text-slate-500 dark:text-slate-400 tracking-widest uppercase">{{ $machine->serial_no }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6">
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-400 uppercase tracking-tight">{{ $machine->company?->name ?? __('System') }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-6">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@php
|
||||
$activeFeatures = [];
|
||||
if ($machine->tax_invoice_enabled) $activeFeatures[] = __('Electronic Invoice');
|
||||
if ($machine->card_terminal_enabled) $activeFeatures[] = __('Includes Credit Card/Mobile Pay');
|
||||
if ($machine->scan_pay_esun_enabled) $activeFeatures[] = __('E.SUN Bank Scan');
|
||||
if ($machine->scan_pay_linepay_enabled) $activeFeatures[] = __('LinePay Payment');
|
||||
if ($machine->shopping_cart_enabled) $activeFeatures[] = __('Shopping Cart');
|
||||
if ($machine->welcome_gift_enabled) $activeFeatures[] = __('Welcome Gift');
|
||||
if ($machine->cash_module_enabled) $activeFeatures[] = __('Coin/Banknote Machine');
|
||||
if ($machine->member_system_enabled) $activeFeatures[] = __('Member System');
|
||||
@endphp
|
||||
|
||||
@forelse($activeFeatures as $feature)
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ $feature }}</span>
|
||||
@empty
|
||||
<span class="px-2.5 py-1 rounded-lg bg-slate-500/10 text-slate-500 dark:text-slate-400 text-[13px] font-bold uppercase tracking-wider">{{ __('None') }}</span>
|
||||
@endforelse
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6 text-right">
|
||||
<button type="button" @click="openMachineSettingsModal({{ json_encode($machine) }})"
|
||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 transition-all border border-transparent hover:border-cyan-500/20"
|
||||
title="{{ __('Edit Settings') }}">
|
||||
<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="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-6 py-24 text-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="w-16 h-16 rounded-2xl bg-slate-50 dark:bg-slate-900 flex items-center justify-center text-slate-300 mb-4">
|
||||
<svg class="w-8 h-8 stroke-[2]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-slate-400 font-bold">{{ __('No machines found') }}</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 3. Pagination -->
|
||||
<div class="mt-8 border-t border-slate-100 dark:border-slate-800 pt-6">
|
||||
{{ $machines->appends(['tab' => 'system_settings', 'search' => request('search'), 'company_id' => request('company_id')])->links('vendor.pagination.luxury') }}
|
||||
</div>
|
||||
</div>
|
||||
@ -28,14 +28,7 @@
|
||||
note: '',
|
||||
settings: {
|
||||
enable_material_code: false,
|
||||
enable_points: false,
|
||||
tax_invoice: false,
|
||||
card_terminal: false,
|
||||
scan_pay_esun: false,
|
||||
scan_pay_linepay: false,
|
||||
shopping_cart: false,
|
||||
welcome_gift: false,
|
||||
cash_module: false
|
||||
enable_points: false
|
||||
}
|
||||
},
|
||||
openCreateModal() {
|
||||
@ -49,14 +42,7 @@
|
||||
status: 1, note: '',
|
||||
settings: {
|
||||
enable_material_code: false,
|
||||
enable_points: false,
|
||||
tax_invoice: false,
|
||||
card_terminal: false,
|
||||
scan_pay_esun: false,
|
||||
scan_pay_linepay: false,
|
||||
shopping_cart: false,
|
||||
welcome_gift: false,
|
||||
cash_module: false
|
||||
enable_points: false
|
||||
}
|
||||
};
|
||||
this.showModal = true;
|
||||
@ -73,14 +59,7 @@
|
||||
software_end_date: company.software_end_date ? company.software_end_date.substring(0, 10) : '',
|
||||
settings: {
|
||||
enable_material_code: company.settings?.enable_material_code || false,
|
||||
enable_points: company.settings?.enable_points || false,
|
||||
tax_invoice: company.settings?.tax_invoice || false,
|
||||
card_terminal: company.settings?.card_terminal || false,
|
||||
scan_pay_esun: company.settings?.scan_pay_esun || false,
|
||||
scan_pay_linepay: company.settings?.scan_pay_linepay || false,
|
||||
shopping_cart: company.settings?.shopping_cart || false,
|
||||
welcome_gift: company.settings?.welcome_gift || false,
|
||||
cash_module: company.settings?.cash_module || false
|
||||
enable_points: company.settings?.enable_points || false
|
||||
}
|
||||
};
|
||||
this.originalStatus = company.status;
|
||||
@ -109,14 +88,7 @@
|
||||
...company,
|
||||
settings: {
|
||||
enable_material_code: company.settings?.enable_material_code || false,
|
||||
enable_points: company.settings?.enable_points || false,
|
||||
tax_invoice: company.settings?.tax_invoice || false,
|
||||
card_terminal: company.settings?.card_terminal || false,
|
||||
scan_pay_esun: company.settings?.scan_pay_esun || false,
|
||||
scan_pay_linepay: company.settings?.scan_pay_linepay || false,
|
||||
shopping_cart: company.settings?.shopping_cart || false,
|
||||
welcome_gift: company.settings?.welcome_gift || false,
|
||||
cash_module: company.settings?.cash_module || false
|
||||
enable_points: company.settings?.enable_points || false
|
||||
}
|
||||
};
|
||||
this.sidebarView = 'detail';
|
||||
@ -127,14 +99,7 @@
|
||||
...company,
|
||||
settings: {
|
||||
enable_material_code: company.settings?.enable_material_code || false,
|
||||
enable_points: company.settings?.enable_points || false,
|
||||
tax_invoice: company.settings?.tax_invoice || false,
|
||||
card_terminal: company.settings?.card_terminal || false,
|
||||
scan_pay_esun: company.settings?.scan_pay_esun || false,
|
||||
scan_pay_linepay: company.settings?.scan_pay_linepay || false,
|
||||
shopping_cart: company.settings?.shopping_cart || false,
|
||||
welcome_gift: company.settings?.welcome_gift || false,
|
||||
cash_module: company.settings?.cash_module || false
|
||||
enable_points: company.settings?.enable_points || false
|
||||
}
|
||||
};
|
||||
this.sidebarView = 'history';
|
||||
@ -148,14 +113,7 @@
|
||||
...company,
|
||||
settings: {
|
||||
enable_material_code: company.settings?.enable_material_code || false,
|
||||
enable_points: company.settings?.enable_points || false,
|
||||
tax_invoice: company.settings?.tax_invoice || false,
|
||||
card_terminal: company.settings?.card_terminal || false,
|
||||
scan_pay_esun: company.settings?.scan_pay_esun || false,
|
||||
scan_pay_linepay: company.settings?.scan_pay_linepay || false,
|
||||
shopping_cart: company.settings?.shopping_cart || false,
|
||||
welcome_gift: company.settings?.welcome_gift || false,
|
||||
cash_module: company.settings?.cash_module || false
|
||||
enable_points: company.settings?.enable_points || false
|
||||
}
|
||||
};
|
||||
this.showHistoryModal = true;
|
||||
@ -165,14 +123,7 @@
|
||||
...company,
|
||||
settings: {
|
||||
enable_material_code: company.settings?.enable_material_code === true || company.settings?.enable_material_code === 1 || company.settings?.enable_material_code === '1',
|
||||
enable_points: company.settings?.enable_points === true || company.settings?.enable_points === 1 || company.settings?.enable_points === '1',
|
||||
tax_invoice: company.settings?.tax_invoice === true || company.settings?.tax_invoice === 1 || company.settings?.tax_invoice === '1',
|
||||
card_terminal: company.settings?.card_terminal === true || company.settings?.card_terminal === 1 || company.settings?.card_terminal === '1',
|
||||
scan_pay_esun: company.settings?.scan_pay_esun === true || company.settings?.scan_pay_esun === 1 || company.settings?.scan_pay_esun === '1',
|
||||
scan_pay_linepay: company.settings?.scan_pay_linepay === true || company.settings?.scan_pay_linepay === 1 || company.settings?.scan_pay_linepay === '1',
|
||||
shopping_cart: company.settings?.shopping_cart === true || company.settings?.shopping_cart === 1 || company.settings?.shopping_cart === '1',
|
||||
welcome_gift: company.settings?.welcome_gift === true || company.settings?.welcome_gift === 1 || company.settings?.welcome_gift === '1',
|
||||
cash_module: company.settings?.cash_module === true || company.settings?.cash_module === 1 || company.settings?.cash_module === '1',
|
||||
enable_points: company.settings?.enable_points === true || company.settings?.enable_points === 1 || company.settings?.enable_points === '1'
|
||||
}
|
||||
};
|
||||
this.showSettingsModal = true;
|
||||
@ -221,11 +172,12 @@
|
||||
}
|
||||
}
|
||||
}">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display transition-all duration-300">
|
||||
{{ __('Customer List') }}
|
||||
{{ __('Customer Management') }}
|
||||
</h1>
|
||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">
|
||||
{{ __('Manage all tenant accounts and validity') }}
|
||||
@ -603,27 +555,6 @@
|
||||
@if($settings['enable_points'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Points') }}</span>
|
||||
@endif
|
||||
@if($settings['tax_invoice'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Electronic Invoice') }}</span>
|
||||
@endif
|
||||
@if($settings['card_terminal'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Card System') }}</span>
|
||||
@endif
|
||||
@if($settings['scan_pay_esun'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('ESUN') }}</span>
|
||||
@endif
|
||||
@if($settings['scan_pay_linepay'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('LinePay') }}</span>
|
||||
@endif
|
||||
@if($settings['shopping_cart'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Shopping Cart') }}</span>
|
||||
@endif
|
||||
@if($settings['welcome_gift'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Welcome Gift') }}</span>
|
||||
@endif
|
||||
@if($settings['cash_module'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Cash Module') }}</span>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
@ -1012,116 +943,6 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 稅務系統 (Taxation System) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Taxation System') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[tax_invoice]" value="0">
|
||||
<input type="checkbox" name="settings[tax_invoice]" value="1" x-model="currentCompany.settings.tax_invoice" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('Electronic Invoice') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 刷卡機系統 (Card Terminal System) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Card Terminal System') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[card_terminal]" value="0">
|
||||
<input type="checkbox" name="settings[card_terminal]" value="1" x-model="currentCompany.settings.card_terminal" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('Includes Credit Card/Mobile Pay') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 掃碼支付功能 (QR Scan Payment) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('QR Scan Payment') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[scan_pay_esun]" value="0">
|
||||
<input type="checkbox" name="settings[scan_pay_esun]" value="1" x-model="currentCompany.settings.scan_pay_esun" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('E.SUN QR Pay') }}</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[scan_pay_linepay]" value="0">
|
||||
<input type="checkbox" name="settings[scan_pay_linepay]" value="1" x-model="currentCompany.settings.scan_pay_linepay" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('LinePay Payment') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 購物車功能 (Shopping Cart Feature) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Shopping Cart Feature') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[shopping_cart]" value="0">
|
||||
<input type="checkbox" name="settings[shopping_cart]" value="1" x-model="currentCompany.settings.shopping_cart" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('Shopping Cart') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 來店禮功能 (Welcome Gift Feature) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Welcome Gift Feature') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[welcome_gift]" value="0">
|
||||
<input type="checkbox" name="settings[welcome_gift]" value="1" x-model="currentCompany.settings.welcome_gift" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('Welcome Gift') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 現金模組功能 (Cash Module Feature) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-center p-4 rounded-xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<div class="md:col-span-1">
|
||||
<h4 class="text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Cash Module Feature') }}</h4>
|
||||
</div>
|
||||
<div class="md:col-span-3 flex flex-wrap gap-x-8 gap-y-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer group">
|
||||
<div class="relative inline-flex items-center">
|
||||
<input type="hidden" name="settings[cash_module]" value="0">
|
||||
<input type="checkbox" name="settings[cash_module]" value="1" x-model="currentCompany.settings.cash_module" class="peer sr-only">
|
||||
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-600 dark:text-slate-300 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{{ __('Coin/Bill Acceptor') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-x-4 pt-8 mt-6 border-t border-slate-100 dark:border-slate-800">
|
||||
@ -1305,97 +1126,7 @@
|
||||
<span x-text="detailCompany.settings?.enable_points ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Electronic Invoice -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-emerald-50 dark:bg-emerald-500/10 text-emerald-500">
|
||||
<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="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="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('Electronic Invoice') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.tax_invoice ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.tax_invoice ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card System -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-blue-50 dark:bg-blue-500/10 text-blue-500">
|
||||
<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="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('Card System') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.card_terminal ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.card_terminal ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ESUN Pay -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-green-50 dark:bg-green-500/10 text-green-500">
|
||||
<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="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 17h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('ESUN QR Pay') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.scan_pay_esun ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.scan_pay_esun ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- LinePay -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-green-50 dark:bg-green-500/10 text-green-500">
|
||||
<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="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('LinePay Payment') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.scan_pay_linepay ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.scan_pay_linepay ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Shopping Cart -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-purple-50 dark:bg-purple-500/10 text-purple-500">
|
||||
<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="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('Shopping Cart') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.shopping_cart ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.shopping_cart ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Welcome Gift -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-rose-50 dark:bg-rose-500/10 text-rose-500">
|
||||
<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="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-14v4m0 0l8 4m-8-4l-8 4m8 4v10l8-4m0-14l-8 4" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('Welcome Gift') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.welcome_gift ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.welcome_gift ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Cash Module -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-amber-50 dark:bg-amber-500/10 text-amber-500">
|
||||
<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="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('Cash Module') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.cash_module ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.cash_module ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Electronic Invoice, Card System, etc. moved to Machine settings -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@ -56,16 +56,16 @@
|
||||
},
|
||||
|
||||
init() {
|
||||
this.$watch('activeTab', (newTab, oldTab) => {
|
||||
const container = document.getElementById('tab-' + newTab + '-container');
|
||||
if (container && container.innerHTML.trim() === '') {
|
||||
this.fetchTabData(newTab);
|
||||
}
|
||||
|
||||
this.$watch('activeTab', (newTab) => {
|
||||
// Update URL
|
||||
const url = new URL(window.location.origin + window.location.pathname);
|
||||
url.searchParams.set('tab', newTab);
|
||||
window.history.pushState({}, '', url);
|
||||
|
||||
// Re-init HSSelect if needed when tab changes
|
||||
this.$nextTick(() => {
|
||||
if (window.HSStaticMethods) window.HSStaticMethods.autoInit();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -90,7 +90,7 @@
|
||||
if (form) {
|
||||
const formData = new FormData(form);
|
||||
formData.forEach((value, key) => {
|
||||
if (value.trim() !== '') params.set(key, value);
|
||||
if (value.trim() !== '') params.append(key, value);
|
||||
});
|
||||
}
|
||||
url = `${window.location.pathname}?${params.toString()}`;
|
||||
@ -185,6 +185,7 @@
|
||||
},
|
||||
|
||||
handleFilterSubmit(tab) {
|
||||
console.log('handleFilterSubmit called for tab:', tab);
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('page');
|
||||
this.fetchTabData(tab);
|
||||
@ -242,7 +243,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<template x-if="activeTab === 'stock-in'">
|
||||
<template x-if="activeTab === 'stock'">
|
||||
<button type="button" @click="openStockInModal()"
|
||||
class="btn-luxury-primary transition-all duration-300">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
@ -257,17 +258,17 @@
|
||||
<div class="flex items-center gap-1 p-1.5 bg-slate-100 dark:bg-slate-900/50 rounded-2xl w-fit border border-slate-200/50 dark:border-slate-800/50"
|
||||
aria-label="Tabs">
|
||||
<button type="button" @click="activeTab = 'stock'"
|
||||
:class="activeTab === 'stock' ? '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="activeTab === 'stock' ? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-sm shadow-cyan-500/20' : '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">
|
||||
{{ __('Current Stocks') }}
|
||||
</button>
|
||||
<button type="button" @click="activeTab = 'stock-in'"
|
||||
:class="activeTab === 'stock-in' ? '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="activeTab === 'stock-in' ? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-sm shadow-cyan-500/20' : '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">
|
||||
{{ __('Stock-In Orders') }}
|
||||
</button>
|
||||
<button type="button" @click="activeTab = 'movements'"
|
||||
:class="activeTab === 'movements' ? '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="activeTab === 'movements' ? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-sm shadow-cyan-500/20' : '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">
|
||||
{{ __('Movement History') }}
|
||||
</button>
|
||||
@ -300,7 +301,7 @@
|
||||
</div>
|
||||
|
||||
<div id="tab-stock-container" class="relative">
|
||||
@if($tab === 'stock') @include('admin.warehouses.partials.tab-stock') @endif
|
||||
@include('admin.warehouses.partials.tab-stock')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -330,7 +331,7 @@
|
||||
</div>
|
||||
|
||||
<div id="tab-stock-in-container" class="relative">
|
||||
@if($tab === 'stock-in') @include('admin.warehouses.partials.tab-stock-in') @endif
|
||||
@include('admin.warehouses.partials.tab-stock-in')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -359,7 +360,7 @@
|
||||
</div>
|
||||
|
||||
<div id="tab-movements-container" class="relative">
|
||||
@if($tab === 'movements') @include('admin.warehouses.partials.tab-movements') @endif
|
||||
@include('admin.warehouses.partials.tab-movements')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -445,7 +446,7 @@
|
||||
<label class="block text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Product') }}</label>
|
||||
<x-searchable-select
|
||||
x-bind:name="'items['+index+'][product_id]'"
|
||||
:options="$products ?? []"
|
||||
:options="$stock_in_products ?? []"
|
||||
placeholder="{{ __('Select Product') }}"
|
||||
/>
|
||||
</div>
|
||||
@ -554,33 +555,33 @@
|
||||
<template x-if="activeOrder">
|
||||
<div class="space-y-10">
|
||||
<!-- Info Grid -->
|
||||
<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-1">
|
||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Status') }}</p>
|
||||
<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-2">
|
||||
<p class="text-xs font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Status') }}</p>
|
||||
<div>
|
||||
<template x-if="activeOrder.status === 'draft'">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-lg text-[10px] font-black bg-amber-500/10 text-amber-500 border border-amber-500/20 uppercase tracking-widest">{{ __('Draft') }}</span>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-lg text-xs font-black bg-amber-500/10 text-amber-500 border border-amber-500/20 uppercase tracking-widest">{{ __('Draft') }}</span>
|
||||
</template>
|
||||
<template x-if="activeOrder.status === 'completed'">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-lg text-[10px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 uppercase tracking-widest">{{ __('Completed') }}</span>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-lg text-xs font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 uppercase tracking-widest">{{ __('Completed') }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Created By') }}</p>
|
||||
<p class="text-xs font-bold text-slate-700 dark:text-slate-300" x-text="activeOrder.creator_name"></p>
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Created By') }}</p>
|
||||
<p class="text-sm font-bold text-slate-700 dark:text-slate-300" x-text="activeOrder.creator_name"></p>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Created At') }}</p>
|
||||
<p class="text-[11px] font-mono font-bold text-slate-600 dark:text-slate-400" x-text="activeOrder.created_at"></p>
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Created At') }}</p>
|
||||
<p class="text-sm font-mono font-bold text-slate-600 dark:text-slate-400" x-text="activeOrder.created_at"></p>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Approved At') }}</p>
|
||||
<p class="text-[11px] font-mono font-bold text-slate-600 dark:text-slate-400" x-text="activeOrder.completed_at || '-'"></p>
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Approved At') }}</p>
|
||||
<p class="text-sm font-mono font-bold text-slate-600 dark:text-slate-400" x-text="activeOrder.completed_at || '-'"></p>
|
||||
</div>
|
||||
<template x-if="activeOrder.note">
|
||||
<div class="col-span-2 space-y-1 pt-2 border-t border-slate-200/50 dark:border-slate-700/50">
|
||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Note') }}</p>
|
||||
<p class="text-xs font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Note') }}</p>
|
||||
<p class="text-xs font-bold text-slate-600 dark:text-slate-400 italic" x-text="activeOrder.note"></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
@submit.prevent="handleFilterSubmit('movements')">
|
||||
<input type="hidden" name="tab" value="movements">
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-4 items-center">
|
||||
<div class="flex flex-wrap gap-4 items-center">
|
||||
<div class="w-64">
|
||||
<x-searchable-select
|
||||
name="warehouse_id"
|
||||
@ -28,12 +28,40 @@
|
||||
@endforeach
|
||||
</x-searchable-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="button" @click="handleFilterSubmit('movements')" class="btn-luxury-secondary px-6">
|
||||
{{ __('Filter') }}
|
||||
</button>
|
||||
{{-- Date Range --}}
|
||||
<div class="relative group w-72"
|
||||
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: 'zh_tw',
|
||||
defaultDate: '{{ request('start_date') }}' && '{{ request('end_date') }}' ? ['{{ request('start_date') }}', '{{ request('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');
|
||||
handleFilterSubmit('movements');
|
||||
} else if (selectedDates.length === 0) {
|
||||
$refs.startDate.value = '';
|
||||
$refs.endDate.value = '';
|
||||
handleFilterSubmit('movements');
|
||||
}
|
||||
}
|
||||
})">
|
||||
<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="{{ request('start_date') }}">
|
||||
<input type="hidden" name="end_date" x-ref="endDate" value="{{ request('end_date') }}">
|
||||
<input type="text" x-ref="dateRange"
|
||||
value="{{ request('start_date') && request('end_date') ? request('start_date') . ' 至 ' . request('end_date') : (request('start_date') ?: '') }}"
|
||||
placeholder="{{ __('Select Date Range') }}" class="luxury-input py-2.5 pl-12 pr-6 block w-full cursor-pointer">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -54,13 +82,13 @@
|
||||
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
||||
<td class="px-6 py-5">
|
||||
<div class="flex items-center gap-x-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 group-hover:bg-indigo-500 group-hover:text-white transition-all duration-300">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition-colors">
|
||||
<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">
|
||||
{{ $mv->product?->name }}
|
||||
</span>
|
||||
<div class="flex items-center gap-2 mt-0.5">
|
||||
@ -77,18 +105,18 @@
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-5 text-center whitespace-nowrap">
|
||||
@php $typeColor = in_array($mv->type, ['in', 'transfer_in']) ? 'emerald' : (in_array($mv->type, ['out', 'transfer_out']) ? 'rose' : 'amber'); @endphp
|
||||
@php $typeColor = in_array($mv->type, ['in', 'transfer_in']) ? 'cyan' : (in_array($mv->type, ['out', 'transfer_out']) ? 'rose' : 'amber'); @endphp
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-xl text-[10px] font-black bg-{{ $typeColor }}-500/10 text-{{ $typeColor }}-500 border border-{{ $typeColor }}-500/20 tracking-widest uppercase shadow-sm shadow-{{ $typeColor }}-500/5">{{ __(\App\Models\Warehouse\StockMovement::TYPE_LABELS[$mv->type] ?? $mv->type) }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-5 text-center font-mono font-extrabold {{ in_array($mv->type, ['in', 'transfer_in']) ? 'text-emerald-500' : 'text-rose-500' }}">
|
||||
<td class="px-6 py-5 text-center font-mono font-extrabold {{ in_array($mv->type, ['in', 'transfer_in']) ? 'text-cyan-500' : 'text-rose-500' }}">
|
||||
<div class="flex flex-col items-center">
|
||||
<span class="text-lg tracking-tighter">{{ in_array($mv->type, ['in', 'transfer_in']) ? '+' : '-' }}{{ $mv->quantity }}</span>
|
||||
<div class="text-[10px] font-bold text-slate-400 dark:text-slate-500 tracking-tight">({{ $mv->before_qty }} → {{ $mv->after_qty }})</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-5 text-center whitespace-nowrap">
|
||||
<span class="text-[11px] font-mono font-bold text-slate-500 dark:text-slate-400 tracking-tighter uppercase">
|
||||
{{ $mv->created_at?->format('Y-m-d H:i') }}
|
||||
<span class="text-xs font-mono font-black text-slate-500 dark:text-slate-400 tracking-widest uppercase">
|
||||
{{ $mv->created_at?->format('Y-m-d H:i:s') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-5 text-right whitespace-nowrap">
|
||||
|
||||
@ -1,26 +1,73 @@
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
{{-- Filters --}}
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-4">
|
||||
<form action="{{ route('admin.warehouses.inventory') }}" method="GET" class="relative group"
|
||||
@submit.prevent="handleFilterSubmit('stock-in')">
|
||||
<input type="hidden" name="tab" value="stock-in">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
<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="{{ request('search') }}"
|
||||
class="py-2.5 pl-12 pr-6 block w-64 luxury-input" placeholder="{{ __('Search order number...') }}">
|
||||
</form>
|
||||
<form action="{{ route('admin.warehouses.inventory') }}" method="GET" class="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-4"
|
||||
@submit.prevent="handleFilterSubmit('stock-in')">
|
||||
<input type="hidden" name="tab" value="stock-in">
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-4 items-center">
|
||||
<div class="relative group">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
<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_order" value="{{ request('search_order') }}"
|
||||
@keydown.enter.prevent="handleFilterSubmit('stock-in')"
|
||||
class="py-2.5 pl-12 pr-6 block w-64 luxury-input" placeholder="{{ __('Search order number...') }}">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="button" @click="handleFilterSubmit('stock-in')" class="btn-luxury-secondary px-6">
|
||||
{{ __('Filter') }}
|
||||
</button>
|
||||
{{-- Date Range --}}
|
||||
<div class="relative group w-72"
|
||||
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: 'zh_tw',
|
||||
defaultDate: '{{ request('start_date') }}' && '{{ request('end_date') }}' ? ['{{ request('start_date') }}', '{{ request('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');
|
||||
handleFilterSubmit('stock-in');
|
||||
} else if (selectedDates.length === 0) {
|
||||
$refs.startDate.value = '';
|
||||
$refs.endDate.value = '';
|
||||
handleFilterSubmit('stock-in');
|
||||
}
|
||||
}
|
||||
})">
|
||||
<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="{{ request('start_date') }}">
|
||||
<input type="hidden" name="end_date" x-ref="endDate" value="{{ request('end_date') }}">
|
||||
<input type="text" x-ref="dateRange"
|
||||
value="{{ request('start_date') && request('end_date') ? request('start_date') . ' 至 ' . request('end_date') : (request('start_date') ?: '') }}"
|
||||
placeholder="{{ __('Select Date Range') }}" class="luxury-input py-2.5 pl-12 pr-6 block w-full cursor-pointer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(isset($stock_in_status_options))
|
||||
<div class="w-48">
|
||||
<x-searchable-select
|
||||
name="status"
|
||||
:selected="request('status')"
|
||||
:placeholder="__('All Status')"
|
||||
x-on:change="handleFilterSubmit('stock-in')"
|
||||
>
|
||||
@foreach($stock_in_status_options as $key => $label)
|
||||
<option value="{{ $key }}" {{ request('status') === $key ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</x-searchable-select>
|
||||
</div>
|
||||
@endif
|
||||
</form>
|
||||
|
||||
{{-- Table --}}
|
||||
<div class="overflow-x-auto">
|
||||
@ -85,8 +132,8 @@
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-5 text-center whitespace-nowrap">
|
||||
<span class="text-[11px] font-mono font-bold text-slate-500 dark:text-slate-400 tracking-tighter">
|
||||
{{ $order->created_at->format('Y-m-d H:i') }}
|
||||
<span class="text-xs font-mono font-black text-slate-500 dark:text-slate-400 tracking-widest">
|
||||
{{ $order->created_at->format('Y-m-d H:i:s') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-5 text-right whitespace-nowrap">
|
||||
|
||||
@ -1,18 +1,23 @@
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<form action="{{ route('admin.warehouses.inventory') }}" method="GET" class="flex flex-col md:flex-row md:items-center gap-4 mb-8"
|
||||
@submit.prevent="handleFilterSubmit('stock')">
|
||||
@submit.prevent="handleFilterSubmit('stock')"
|
||||
@multi-select-changed.window="handleFilterSubmit('stock')">
|
||||
<input type="hidden" name="tab" value="stock">
|
||||
<div class="relative group">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10"><svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><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="{{ request('search') }}" class="py-2.5 pl-12 pr-6 block w-64 luxury-input" placeholder="{{ __('Search products...') }}">
|
||||
<input type="text" name="search" value="{{ request('search') }}"
|
||||
class="py-2.5 pl-12 pr-6 block w-64 luxury-input"
|
||||
placeholder="{{ __('Search products...') }}"
|
||||
@keydown.enter.prevent="handleFilterSubmit('stock')">
|
||||
</div>
|
||||
<div class="min-w-[240px]">
|
||||
<x-searchable-select
|
||||
name="warehouse_id"
|
||||
:options="$warehouses"
|
||||
:selected="request('warehouse_id')"
|
||||
|
||||
{{-- 多選倉庫篩選器 --}}
|
||||
<div class="min-w-[260px]">
|
||||
<x-multi-select
|
||||
name="warehouse_ids"
|
||||
:options="$warehouses"
|
||||
:selected="request('warehouse_ids', [])"
|
||||
:placeholder="__('Filter by Warehouse Presence')"
|
||||
@change="handleFilterSubmit('stock')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
@ -21,37 +26,49 @@
|
||||
<table class="w-full text-left border-separate border-spacing-y-0 min-w-max">
|
||||
<thead>
|
||||
<tr class="bg-slate-50/50 dark:bg-white/[0.02]">
|
||||
<th class="px-6 py-5 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 sticky left-0 bg-white dark:bg-[#1e293b] z-20">{{ __('Product') }}</th>
|
||||
<th class="px-6 py-5 text-sm font-black text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 sticky left-0 bg-white dark:bg-[#1e293b] z-20 w-[180px] md:w-[260px] min-w-[180px] md:min-w-[260px]">{{ __('Product') }}</th>
|
||||
<th class="px-6 py-5 text-sm font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.1em] border-b border-slate-100 dark:border-slate-800 text-center bg-white dark:bg-[#1e293b] sticky md:left-[260px] z-20 border-r border-slate-200/50 dark:border-slate-700/50 shadow-[1px_0_0_0_rgba(0,0,0,0.05)] dark:shadow-[1px_0_0_0_rgba(255,255,255,0.05)]">
|
||||
{{ __('Total') }}
|
||||
</th>
|
||||
@foreach($inventory_warehouses as $w)
|
||||
<th class="px-6 py-5 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-center">
|
||||
<th class="px-6 py-5 text-sm font-black text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<span class="text-slate-800 dark:text-slate-200">{{ $w->name }}</span>
|
||||
<span class="text-[9px] opacity-60 tracking-normal mt-0.5">{{ $w->type === 'main' ? __('Main') : __('Branch') }}</span>
|
||||
<span class="text-base font-black text-slate-800 dark:text-slate-200 tracking-tight">{{ $w->name }}</span>
|
||||
<span class="text-[10px] font-bold opacity-60 tracking-widest mt-0.5 uppercase">{{ $w->type === 'main' ? __('Main') : __('Branch') }}</span>
|
||||
</div>
|
||||
</th>
|
||||
@endforeach
|
||||
<th class="px-6 py-5 text-xs font-bold text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center bg-cyan-50/30 dark:bg-cyan-500/5">{{ __('Total') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-white/[0.05]">
|
||||
@forelse($products as $product)
|
||||
@php $totalQuantity = $product->stocks->sum('quantity'); @endphp
|
||||
@php
|
||||
$viewableWarehouseIds = $inventory_warehouses->pluck('id')->toArray();
|
||||
$totalQuantity = $product->stocks->whereIn('warehouse_id', $viewableWarehouseIds)->sum('quantity');
|
||||
@endphp
|
||||
<tr class="group hover:bg-slate-50 dark:hover:bg-white/[0.03] transition-all duration-300">
|
||||
<td class="px-6 py-4 sticky left-0 bg-white dark:bg-[#1e293b] group-hover:bg-slate-50 dark:group-hover:bg-white/[0.03] z-10 transition-colors shadow-[4px_0_12px_-4px_rgba(0,0,0,0.05)] dark:shadow-[4px_0_12px_-4px_rgba(0,0,0,0.2)]">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-11 h-11 rounded-2xl bg-slate-100 dark:bg-slate-800/50 flex items-center justify-center overflow-hidden border border-slate-200/50 dark:border-white/5 group-hover:border-cyan-500/30 transition-colors">
|
||||
<td class="px-6 py-4 sticky left-0 bg-white dark:bg-[#1e293b] group-hover:bg-slate-50 dark:group-hover:bg-white/[0.03] z-10 transition-colors w-[180px] md:w-[260px] min-w-[180px] md:min-w-[260px]">
|
||||
<div class="flex items-center gap-3 md:gap-4">
|
||||
<div class="w-8 h-8 md:w-11 md:h-11 rounded-xl md:rounded-2xl bg-slate-100 dark:bg-slate-800/50 flex items-center justify-center overflow-hidden border border-slate-200/50 dark:border-white/5 group-hover:border-cyan-500/30 transition-colors shrink-0">
|
||||
@if($product->image_url)
|
||||
<img src="{{ $product->image_url }}" class="w-full h-full object-cover">
|
||||
@else
|
||||
<svg class="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" /></svg>
|
||||
<svg class="w-4 h-4 md:w-5 md:h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" /></svg>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">{{ $product->localized_name }}</span>
|
||||
<span class="text-[10px] font-bold text-slate-400 mt-0.5 tracking-tight">{{ $product->barcode ?? '-' }}</span>
|
||||
<div class="flex flex-col min-w-0">
|
||||
<span class="text-xs md:text-sm font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors truncate">{{ $product->localized_name }}</span>
|
||||
@if($product->barcode)
|
||||
<span class="text-[9px] md:text-[10px] font-bold text-slate-400 mt-0.5 tracking-tight truncate">{{ $product->barcode }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 md:px-6 py-4 text-center bg-white dark:bg-[#1e293b] group-hover:bg-slate-50 dark:group-hover:bg-white/[0.03] transition-colors sticky md:left-[260px] z-10 border-r border-slate-200/50 dark:border-slate-700/50 shadow-[4px_0_12px_-4px_rgba(0,0,0,0.1)] dark:shadow-[4px_0_12px_-4px_rgba(0,0,0,0.3)]">
|
||||
<span class="text-lg md:text-xl font-mono font-black text-cyan-600 dark:text-cyan-400 drop-shadow-[0_0_10px_rgba(6,182,212,0.1)]">
|
||||
{{ $totalQuantity }}
|
||||
</span>
|
||||
</td>
|
||||
@foreach($inventory_warehouses as $w)
|
||||
@php
|
||||
$stock = $product->stocks->firstWhere('warehouse_id', $w->id);
|
||||
@ -72,11 +89,6 @@
|
||||
@endif
|
||||
</td>
|
||||
@endforeach
|
||||
<td class="px-6 py-4 text-center bg-cyan-50/30 dark:bg-cyan-500/5 group-hover:bg-cyan-100/30 dark:group-hover:bg-cyan-500/10 transition-colors">
|
||||
<span class="text-xl font-mono font-black text-cyan-600 dark:text-cyan-400 drop-shadow-[0_0_10px_rgba(6,182,212,0.1)]">
|
||||
{{ $totalQuantity }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
|
||||
@ -89,6 +89,12 @@
|
||||
'machine-models' => __('Machine Models'),
|
||||
default => null,
|
||||
},
|
||||
'permission' => match($segments[2] ?? '') {
|
||||
'companies' => __('Customer Management'),
|
||||
'accounts' => __('Account Management'),
|
||||
'roles' => __('Roles'),
|
||||
default => null,
|
||||
},
|
||||
default => null,
|
||||
},
|
||||
'edit' => str_starts_with($routeName, 'profile') ? __('Profile') : __('Edit'),
|
||||
|
||||
176
resources/views/components/multi-select.blade.php
Normal file
176
resources/views/components/multi-select.blade.php
Normal file
@ -0,0 +1,176 @@
|
||||
{{--
|
||||
多選下拉組件 (Multi-Select Dropdown)
|
||||
純 Alpine.js 實作,不依賴 Preline HSSelect,避免 tags 模式衝突。
|
||||
|
||||
使用方式:
|
||||
<x-multi-select
|
||||
name="warehouse_ids"
|
||||
:options="$warehouses"
|
||||
:selected="request('warehouse_ids', [])"
|
||||
:placeholder="__('Filter by Warehouse')"
|
||||
/>
|
||||
|
||||
Props:
|
||||
- name : 表單欄位名稱,會自動加上 [] (string)
|
||||
- options : 選項列表,支援 Collection<{id, name}> 或 associative array (iterable)
|
||||
- selected : 已選值陣列 (array)
|
||||
- placeholder : 未選擇時的提示文字 (string)
|
||||
- id : 元素 ID (string, optional)
|
||||
- hasSearch : 是否顯示搜尋框 (bool, default: true)
|
||||
|
||||
事件:
|
||||
- 選項變更後會觸發外層 form 的 submit 事件(400ms debounce)
|
||||
- 如不需自動提交,加上 no-auto-submit 屬性
|
||||
--}}
|
||||
|
||||
@props([
|
||||
'name' => null,
|
||||
'options' => [],
|
||||
'selected' => [],
|
||||
'placeholder' => null,
|
||||
'id' => null,
|
||||
'hasSearch' => true,
|
||||
])
|
||||
|
||||
@php
|
||||
$id = $id ?? $name ?? 'multi-select-' . uniqid();
|
||||
$options = is_iterable($options) ? $options : [];
|
||||
$placeholder = $placeholder ?: __('Select...');
|
||||
$autoSubmit = !$attributes->has('no-auto-submit');
|
||||
|
||||
// 將選項統一為 [{id, name}] 格式
|
||||
$normalizedOptions = collect($options)->map(function ($item, $key) {
|
||||
if (is_object($item)) {
|
||||
return ['id' => $item->id ?? $item->value ?? $key, 'name' => $item->name ?? $item->label ?? $item->title ?? $key];
|
||||
}
|
||||
return ['id' => $key, 'name' => $item];
|
||||
})->values()->toArray();
|
||||
|
||||
// 確保 selected 是陣列
|
||||
$selectedArray = is_array($selected) ? array_map('intval', $selected) : [];
|
||||
@endphp
|
||||
|
||||
<div id="{{ $id }}"
|
||||
class="relative {{ $attributes->get('class', '') }}"
|
||||
x-data="{
|
||||
open: false,
|
||||
search: '',
|
||||
selected: @js($selectedArray),
|
||||
options: @js($normalizedOptions),
|
||||
autoSubmit: @js($autoSubmit),
|
||||
|
||||
get filtered() {
|
||||
if (!this.search) return this.options;
|
||||
const s = this.search.toLowerCase();
|
||||
return this.options.filter(o => o.name.toString().toLowerCase().includes(s));
|
||||
},
|
||||
isSelected(id) { return this.selected.includes(id); },
|
||||
toggle(id) {
|
||||
const i = this.selected.indexOf(id);
|
||||
if (i === -1) { this.selected.push(id); } else { this.selected.splice(i, 1); }
|
||||
this.onChanged();
|
||||
},
|
||||
remove(id) {
|
||||
this.selected = this.selected.filter(v => v !== id);
|
||||
this.onChanged();
|
||||
},
|
||||
selectAll() {
|
||||
this.selected = this.options.map(o => o.id);
|
||||
this.onChanged();
|
||||
},
|
||||
clear() {
|
||||
this.selected = [];
|
||||
this.onChanged();
|
||||
},
|
||||
onChanged() {
|
||||
if (!this.autoSubmit) return;
|
||||
this.$nextTick(() => {
|
||||
window.dispatchEvent(new CustomEvent('multi-select-changed'));
|
||||
});
|
||||
},
|
||||
getName(id) {
|
||||
const o = this.options.find(o => o.id === id);
|
||||
return o ? o.name : id;
|
||||
}
|
||||
}"
|
||||
@click.outside="open = false"
|
||||
@keydown.escape.window="open = false"
|
||||
{{ $attributes->except(['class', 'options', 'selected', 'placeholder', 'id', 'name', 'hasSearch', 'no-auto-submit']) }}>
|
||||
|
||||
{{-- 隱藏 input 送出表單用 --}}
|
||||
<template x-for="id in selected" :key="id">
|
||||
<input type="hidden" name="{{ $name }}[]" :value="id">
|
||||
</template>
|
||||
|
||||
{{-- Toggle 按鈕 --}}
|
||||
<button type="button" @click="open = !open"
|
||||
class="luxury-input w-full pr-10 cursor-pointer text-start flex flex-wrap items-center gap-1.5 relative min-h-[46px]">
|
||||
|
||||
{{-- 未選擇時的 placeholder --}}
|
||||
<span x-show="selected.length === 0" class="text-slate-400 dark:text-slate-500 font-bold text-sm">
|
||||
{{ $placeholder }}
|
||||
</span>
|
||||
|
||||
{{-- 選擇後的 tags --}}
|
||||
<template x-for="id in selected" :key="'tag-'+id">
|
||||
<span class="inline-flex items-center gap-1 bg-cyan-500/10 dark:bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 rounded-lg px-2.5 py-1 text-xs font-bold border border-cyan-500/20 dark:border-cyan-500/30">
|
||||
<span x-text="getName(id)"></span>
|
||||
<button type="button" @click.stop="remove(id)" class="hover:text-cyan-500 transition-colors">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
{{-- 下拉箭頭 --}}
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
|
||||
<svg class="size-4 text-slate-400 transition-transform duration-300" :class="open && 'rotate-180'" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{{-- 下拉選單 --}}
|
||||
<div x-show="open"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 -translate-y-1"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="absolute z-[150] mt-2 w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-white/10 rounded-xl shadow-[0_20px_50px_rgba(0,0,0,0.3)] overflow-hidden"
|
||||
x-cloak>
|
||||
|
||||
{{-- 搜尋框 --}}
|
||||
@if($hasSearch)
|
||||
<div class="sticky top-0 bg-white/95 dark:bg-slate-900/95 backdrop-blur-md p-2 z-10">
|
||||
<input type="text" x-model="search"
|
||||
class="block w-full py-2 px-3 text-sm border-slate-200 dark:border-white/10 rounded-lg focus:border-cyan-500 focus:ring-cyan-500 bg-slate-50 dark:bg-slate-900/50 dark:text-slate-200 placeholder:text-slate-400 dark:placeholder:text-slate-500"
|
||||
placeholder="{{ __('Search...') }}" @click.stop>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- 全選 / 清除 --}}
|
||||
<div class="flex items-center justify-between px-3 py-1.5 border-b border-slate-100 dark:border-slate-800">
|
||||
<button type="button" @click.stop="selectAll()"
|
||||
class="text-[11px] font-bold text-cyan-600 dark:text-cyan-400 hover:underline">{{ __('Select All') }}</button>
|
||||
<button type="button" @click.stop="clear()"
|
||||
class="text-[11px] font-bold text-slate-400 hover:text-rose-500 hover:underline transition-colors">{{ __('Clear') }}</button>
|
||||
</div>
|
||||
|
||||
{{-- 選項列表 --}}
|
||||
<div class="max-h-60 overflow-y-auto p-1.5">
|
||||
<template x-for="o in filtered" :key="o.id">
|
||||
<label @click.stop class="flex items-center gap-3 py-2.5 px-3 rounded-lg cursor-pointer hover:bg-slate-50 dark:hover:bg-cyan-500/10 transition-all duration-200 mb-0.5"
|
||||
:class="isSelected(o.id) && 'bg-cyan-50 dark:bg-cyan-500/5'">
|
||||
<input type="checkbox" :checked="isSelected(o.id)" @change="toggle(o.id)"
|
||||
class="rounded border-slate-300 dark:border-slate-600 text-cyan-500 focus:ring-cyan-500 dark:bg-slate-800 cursor-pointer">
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300" x-text="o.name"></span>
|
||||
<svg x-show="isSelected(o.id)" class="w-4 h-4 text-cyan-500 ml-auto shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
{{-- 無搜尋結果 --}}
|
||||
<div x-show="filtered.length === 0" class="py-6 text-center text-sm text-slate-400 font-bold">
|
||||
{{ __('No results found') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -213,6 +213,7 @@ Route::middleware(['auth', 'verified', 'tenant.access'])->prefix('admin')->name(
|
||||
Route::put('/{machine}', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'update'])->name('update');
|
||||
Route::post('/', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'store'])->name('store');
|
||||
Route::post('/{machine}/regenerate-token', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'regenerateToken'])->name('regenerate-token');
|
||||
Route::patch('/{machine}/update-system-settings', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'updateSystemSettings'])->name('update-system-settings');
|
||||
|
||||
// 地址轉座標 (Geocoding Proxy)
|
||||
Route::post('/geocode', [App\Http\Controllers\Admin\GeocodingController::class, 'resolve'])->name('geocode');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user