[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();
|
$machines = $machineQuery->latest()->paginate($per_page)->withQueryString();
|
||||||
break;
|
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':
|
case 'models':
|
||||||
$modelQuery = MachineModel::query()->withCount('machines');
|
$modelQuery = MachineModel::query()->withCount('machines');
|
||||||
if ($search) {
|
if ($search) {
|
||||||
@ -70,6 +81,7 @@ class MachineSettingController extends AdminController
|
|||||||
}
|
}
|
||||||
$users_list = $userQuery->latest()->paginate($per_page)->withQueryString();
|
$users_list = $userQuery->latest()->paginate($per_page)->withQueryString();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$companies = \App\Models\System\Company::select('id', 'name', 'code')->get();
|
$companies = \App\Models\System\Company::select('id', 'name', 'code')->get();
|
||||||
@ -77,6 +89,7 @@ class MachineSettingController extends AdminController
|
|||||||
$tabView = match($tab) {
|
$tabView = match($tab) {
|
||||||
'models' => 'admin.basic-settings.machines.partials.tab-models',
|
'models' => 'admin.basic-settings.machines.partials.tab-models',
|
||||||
'permissions' => 'admin.basic-settings.machines.partials.tab-permissions',
|
'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',
|
default => 'admin.basic-settings.machines.partials.tab-machines',
|
||||||
};
|
};
|
||||||
return response()->view($tabView, compact(
|
return response()->view($tabView, compact(
|
||||||
@ -194,6 +207,12 @@ class MachineSettingController extends AdminController
|
|||||||
'is_spring_slot_41_50' => 'boolean',
|
'is_spring_slot_41_50' => 'boolean',
|
||||||
'is_spring_slot_51_60' => 'boolean',
|
'is_spring_slot_51_60' => 'boolean',
|
||||||
'member_system_enabled' => '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',
|
'machine_model_id' => 'required|exists:machine_models,id',
|
||||||
'payment_config_id' => 'nullable|exists:payment_configs,id',
|
'payment_config_id' => 'nullable|exists:payment_configs,id',
|
||||||
'location' => 'nullable|string|max:255',
|
'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'])) {
|
if (isset($validated['settings'])) {
|
||||||
$validated['settings']['enable_material_code'] = filter_var($validated['settings']['enable_material_code'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
$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']['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) {
|
DB::transaction(function () use ($validated) {
|
||||||
@ -185,13 +178,6 @@ class CompanyController extends Controller
|
|||||||
if (isset($validated['settings'])) {
|
if (isset($validated['settings'])) {
|
||||||
$validated['settings']['enable_material_code'] = filter_var($validated['settings']['enable_material_code'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
$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']['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) {
|
DB::transaction(function () use ($validated, $company) {
|
||||||
@ -230,13 +216,6 @@ class CompanyController extends Controller
|
|||||||
$formattedSettings = [
|
$formattedSettings = [
|
||||||
'enable_material_code' => filter_var($settings['enable_material_code'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
'enable_material_code' => filter_var($settings['enable_material_code'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||||
'enable_points' => filter_var($settings['enable_points'] ?? 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
|
// Ensure we force save the JSON cast properly
|
||||||
|
|||||||
@ -147,20 +147,26 @@ class WarehouseController extends Controller
|
|||||||
|
|
||||||
public function inventory(Request $request)
|
public function inventory(Request $request)
|
||||||
{
|
{
|
||||||
$tab = $request->input('tab', 'stock-in');
|
$tab = $request->input('tab', 'stock');
|
||||||
$warehouses = Warehouse::active()->orderBy('name')->get();
|
$warehouses = Warehouse::active()->orderBy('name')->get();
|
||||||
|
$isAjax = $request->ajax();
|
||||||
|
|
||||||
$data = compact('warehouses', 'tab');
|
$data = compact('warehouses', 'tab');
|
||||||
|
|
||||||
if ($tab === 'stock') {
|
// 1. Current Stocks (stock)
|
||||||
$inventory_warehouses = Warehouse::active()->orderBy('name')->get(['id', 'name', 'type']);
|
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) {
|
$query = Product::with(['stocks' => function($q) {
|
||||||
$q->select('id', 'product_id', 'warehouse_id', 'quantity', 'safety_stock');
|
$q->select('id', 'product_id', 'warehouse_id', 'quantity', 'safety_stock');
|
||||||
}, 'translations']);
|
}, 'translations']);
|
||||||
|
|
||||||
if ($request->filled('warehouse_id')) {
|
if ($warehouse_ids) {
|
||||||
$query->whereHas('stocks', fn($q) => $q->where('warehouse_id', $request->input('warehouse_id')));
|
$query->whereHas('stocks', fn($q) => $q->whereIn('warehouse_id', (array)$warehouse_ids));
|
||||||
}
|
}
|
||||||
if ($search = $request->input('search')) {
|
if ($search = $request->input('search')) {
|
||||||
$query->where('name', 'like', "%{$search}%");
|
$query->where('name', 'like', "%{$search}%");
|
||||||
@ -168,20 +174,30 @@ class WarehouseController extends Controller
|
|||||||
|
|
||||||
$data['inventory_warehouses'] = $inventory_warehouses;
|
$data['inventory_warehouses'] = $inventory_warehouses;
|
||||||
$data['products'] = $query->orderBy('name')
|
$data['products'] = $query->orderBy('name')
|
||||||
->paginate($request->input('per_page', 15))
|
->paginate($request->input('per_page', 15), ['*'], 'stock_page')
|
||||||
->withQueryString();
|
->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'])
|
$query = StockInOrder::with(['warehouse:id,name', 'creator:id,name'])
|
||||||
->latest();
|
->latest();
|
||||||
|
|
||||||
if ($request->filled('status')) {
|
if ($request->filled('status')) {
|
||||||
$query->where('status', $request->input('status'));
|
$query->where('status', $request->input('status'));
|
||||||
}
|
}
|
||||||
$data['orders'] = $query->paginate($request->input('per_page', 10))->withQueryString();
|
if ($search = $request->input('search_order')) {
|
||||||
$data['products'] = Product::orderBy('name')->get(['id', 'name', 'image_url']);
|
$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'])
|
$query = StockMovement::with(['warehouse:id,name', 'product:id,name', 'creator:id,name'])
|
||||||
->orderBy('created_at', 'desc');
|
->orderBy('created_at', 'desc');
|
||||||
|
|
||||||
@ -191,10 +207,13 @@ class WarehouseController extends Controller
|
|||||||
if ($request->filled('type')) {
|
if ($request->filled('type')) {
|
||||||
$query->where('type', $request->input('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([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'tab' => $tab,
|
'tab' => $tab,
|
||||||
|
|||||||
@ -85,6 +85,12 @@ class Machine extends Model
|
|||||||
'images',
|
'images',
|
||||||
'creator_id',
|
'creator_id',
|
||||||
'updater_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'];
|
protected $appends = ['image_urls', 'calculated_status'];
|
||||||
@ -177,6 +183,12 @@ class Machine extends Model
|
|||||||
'is_spring_slot_41_50' => 'boolean',
|
'is_spring_slot_41_50' => 'boolean',
|
||||||
'is_spring_slot_51_60' => 'boolean',
|
'is_spring_slot_51_60' => 'boolean',
|
||||||
'member_system_enabled' => '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',
|
'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 Open Dashboard": "Click to Open Dashboard",
|
||||||
"Click to upload": "Click to upload",
|
"Click to upload": "Click to upload",
|
||||||
"Close Panel": "Close Panel",
|
"Close Panel": "Close Panel",
|
||||||
|
"Coin\/Banknote Machine": "Coin\/Banknote Machine",
|
||||||
"Coin\/Bill Acceptor": "Coin\/Bill Acceptor",
|
"Coin\/Bill Acceptor": "Coin\/Bill Acceptor",
|
||||||
"Command Center": "Command Center",
|
"Command Center": "Command Center",
|
||||||
"Command Confirmation": "Command Confirmation",
|
"Command Confirmation": "Command Confirmation",
|
||||||
@ -264,6 +265,7 @@
|
|||||||
"Created By": "Created By",
|
"Created By": "Created By",
|
||||||
"Creation Time": "Creation Time",
|
"Creation Time": "Creation Time",
|
||||||
"Creator": "Creator",
|
"Creator": "Creator",
|
||||||
|
"Credit Card": "Credit Card",
|
||||||
"Credit Card \/ Contactless": "Credit Card \/ Contactless",
|
"Credit Card \/ Contactless": "Credit Card \/ Contactless",
|
||||||
"Critical": "Critical",
|
"Critical": "Critical",
|
||||||
"Current": "Current",
|
"Current": "Current",
|
||||||
@ -334,6 +336,8 @@
|
|||||||
"Draft": "Draft",
|
"Draft": "Draft",
|
||||||
"Duration": "Duration",
|
"Duration": "Duration",
|
||||||
"Duration (Seconds)": "Duration (Seconds)",
|
"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 Pay": "E.SUN QR Pay",
|
||||||
"E.SUN QR Scan": "E.SUN QR Scan",
|
"E.SUN QR Scan": "E.SUN QR Scan",
|
||||||
"E.SUN QR Scan Settings Description": "E.SUN Bank QR Scan Payment Settings",
|
"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 products found matching your criteria.": "No products found matching your criteria.",
|
||||||
"No records found": "No records found",
|
"No records found": "No records found",
|
||||||
"No replenishment orders found": "No replenishment orders found",
|
"No replenishment orders found": "No replenishment orders found",
|
||||||
|
"No results found": "No results found",
|
||||||
"No roles available": "No roles available",
|
"No roles available": "No roles available",
|
||||||
"No roles found.": "No roles found.",
|
"No roles found.": "No roles found.",
|
||||||
"No slot data": "No slot data",
|
"No slot data": "No slot data",
|
||||||
@ -1027,6 +1032,7 @@
|
|||||||
"Serial Number": "Serial Number",
|
"Serial Number": "Serial Number",
|
||||||
"Service Periods": "Service Periods",
|
"Service Periods": "Service Periods",
|
||||||
"Service Terms": "Service Periods",
|
"Service Terms": "Service Periods",
|
||||||
|
"Settings updated successfully.": "Settings updated successfully.",
|
||||||
"Settlement": "Settlement",
|
"Settlement": "Settlement",
|
||||||
"Shopping Cart": "Shopping Cart",
|
"Shopping Cart": "Shopping Cart",
|
||||||
"Shopping Cart Feature": "Shopping Cart Feature",
|
"Shopping Cart Feature": "Shopping Cart Feature",
|
||||||
@ -1213,6 +1219,7 @@
|
|||||||
"Update Product": "Update Product",
|
"Update Product": "Update Product",
|
||||||
"Update Settings": "Update Settings",
|
"Update Settings": "Update Settings",
|
||||||
"Update existing role and permissions.": "Update existing role and permissions.",
|
"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 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.",
|
"Update your account's profile information and email address.": "Update your account's profile information and email address.",
|
||||||
"Upload Image": "Upload Image",
|
"Upload Image": "Upload Image",
|
||||||
|
|||||||
16
lang/ja.json
16
lang/ja.json
@ -18,6 +18,7 @@
|
|||||||
"Card Terminal System": "カード端末システム",
|
"Card Terminal System": "カード端末システム",
|
||||||
"Cash Module": "現金決済モジュール",
|
"Cash Module": "現金決済モジュール",
|
||||||
"Cash Module Feature": "現金モジュール機能",
|
"Cash Module Feature": "現金モジュール機能",
|
||||||
|
"Coin\/Banknote Machine": "硬貨\/紙幣モジュール",
|
||||||
"Coin\/Bill Acceptor": "コイン\/紙幣機",
|
"Coin\/Bill Acceptor": "コイン\/紙幣機",
|
||||||
"Completed": "完了",
|
"Completed": "完了",
|
||||||
"Confirm": "確認",
|
"Confirm": "確認",
|
||||||
@ -31,10 +32,13 @@
|
|||||||
"Create stock transfers between warehouses and machine returns": "倉庫間の転送および機台からの返品伝票を作成",
|
"Create stock transfers between warehouses and machine returns": "倉庫間の転送および機台からの返品伝票を作成",
|
||||||
"Created At": "作成日時",
|
"Created At": "作成日時",
|
||||||
"Created By": "作成者",
|
"Created By": "作成者",
|
||||||
|
"Credit Card": "クレジットカード",
|
||||||
"Damage": "破損",
|
"Damage": "破損",
|
||||||
"Delivering": "配送中",
|
"Delivering": "配送中",
|
||||||
"Display Material Code": "素材コードを表示",
|
"Display Material Code": "素材コードを表示",
|
||||||
"Draft": "下書き",
|
"Draft": "下書き",
|
||||||
|
"E.SUN Bank Scan": "玉山銀行スキャン",
|
||||||
|
"E.SUN Pay": "E.SUN Pay",
|
||||||
"E.SUN QR Pay": "玉山QR決済",
|
"E.SUN QR Pay": "玉山QR決済",
|
||||||
"ESUN": "玉山銀行 (ESUN)",
|
"ESUN": "玉山銀行 (ESUN)",
|
||||||
"Electronic Invoice": "電子翻訳",
|
"Electronic Invoice": "電子翻訳",
|
||||||
@ -52,10 +56,12 @@
|
|||||||
"Includes Credit Card\/Mobile Pay": "クレジットカード\/モバイル決済を含む",
|
"Includes Credit Card\/Mobile Pay": "クレジットカード\/モバイル決済を含む",
|
||||||
"Inventory Management": "在庫管理",
|
"Inventory Management": "在庫管理",
|
||||||
"LinePay": "LinePay",
|
"LinePay": "LinePay",
|
||||||
"LinePay Payment": "LinePay決済",
|
"LinePay Payment": "LinePay 決済",
|
||||||
"Loading...": "読み込み中...",
|
"Loading...": "読み込み中...",
|
||||||
"Low Stock": "在庫不足",
|
"Low Stock": "在庫不足",
|
||||||
"Low Stock Alerts": "低在庫アラート",
|
"Low Stock Alerts": "低在庫アラート",
|
||||||
|
"Machine Distribution": "機器分布",
|
||||||
|
"Machine Distribution Map": "機器分布マップ",
|
||||||
"Machine Inventory Overview": "機台在庫概要",
|
"Machine Inventory Overview": "機台在庫概要",
|
||||||
"Machine Replenishment": "機台補充",
|
"Machine Replenishment": "機台補充",
|
||||||
"Machine Return": "機台返品",
|
"Machine Return": "機台返品",
|
||||||
@ -113,6 +119,7 @@
|
|||||||
"Select Product": "商品を選択",
|
"Select Product": "商品を選択",
|
||||||
"Select Warehouse": "倉庫を選択",
|
"Select Warehouse": "倉庫を選択",
|
||||||
"Serial No.": "シリアル番号",
|
"Serial No.": "シリアル番号",
|
||||||
|
"Settings updated successfully.": "設定が正常に更新されました。",
|
||||||
"Shopping Cart": "ショッピングカート",
|
"Shopping Cart": "ショッピングカート",
|
||||||
"Shopping Cart Feature": "ショッピングカート機能",
|
"Shopping Cart Feature": "ショッピングカート機能",
|
||||||
"Slot": "貨道",
|
"Slot": "貨道",
|
||||||
@ -145,6 +152,7 @@
|
|||||||
"Transfers": "転送",
|
"Transfers": "転送",
|
||||||
"Unpublish": "配信終了",
|
"Unpublish": "配信終了",
|
||||||
"Update Settings": "設定を更新",
|
"Update Settings": "設定を更新",
|
||||||
|
"Update failed": "更新に失敗しました",
|
||||||
"Video": "動画",
|
"Video": "動画",
|
||||||
"View Details": "詳細を表示",
|
"View Details": "詳細を表示",
|
||||||
"View Inventory": "在庫を表示",
|
"View Inventory": "在庫を表示",
|
||||||
@ -161,5 +169,9 @@
|
|||||||
"Warehouse to Warehouse": "倉庫間転送",
|
"Warehouse to Warehouse": "倉庫間転送",
|
||||||
"Warehouse updated successfully.": "倉庫が正常に更新されました。",
|
"Warehouse updated successfully.": "倉庫が正常に更新されました。",
|
||||||
"Welcome Gift": "登録特典",
|
"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 model that is currently in use by machines.": "無法刪除目前正在被機台使用的型號。",
|
||||||
"Cannot delete role with active users.": "無法刪除已有綁定帳號的角色。",
|
"Cannot delete role with active users.": "無法刪除已有綁定帳號的角色。",
|
||||||
"Cannot delete warehouse with existing stock": "無法刪除仍有庫存的倉庫",
|
"Cannot delete warehouse with existing stock": "無法刪除仍有庫存的倉庫",
|
||||||
|
"Card Machine System": "刷卡機系統",
|
||||||
"Card Reader": "刷卡機",
|
"Card Reader": "刷卡機",
|
||||||
"Card Reader No": "刷卡機編號",
|
"Card Reader No": "刷卡機編號",
|
||||||
"Card Reader Reboot": "刷卡機重啟",
|
"Card Reader Reboot": "刷卡機重啟",
|
||||||
@ -175,6 +176,7 @@
|
|||||||
"Card Terminal System": "刷卡機系統",
|
"Card Terminal System": "刷卡機系統",
|
||||||
"Cash Module": "硬幣機 \/ 紙鈔機模組",
|
"Cash Module": "硬幣機 \/ 紙鈔機模組",
|
||||||
"Cash Module Feature": "現金模組功能",
|
"Cash Module Feature": "現金模組功能",
|
||||||
|
"Cash Module Function": "現金模組功能",
|
||||||
"Category": "類別",
|
"Category": "類別",
|
||||||
"Category Management": "分類管理",
|
"Category Management": "分類管理",
|
||||||
"Category Name": "分類名稱",
|
"Category Name": "分類名稱",
|
||||||
@ -198,6 +200,7 @@
|
|||||||
"Click to Open Dashboard": "點擊開啟儀表板",
|
"Click to Open Dashboard": "點擊開啟儀表板",
|
||||||
"Click to upload": "點擊上傳",
|
"Click to upload": "點擊上傳",
|
||||||
"Close Panel": "關閉面板",
|
"Close Panel": "關閉面板",
|
||||||
|
"Coin\/Banknote Machine": "硬幣機\/紙鈔機",
|
||||||
"Coin\/Bill Acceptor": "硬幣機\/紙鈔機",
|
"Coin\/Bill Acceptor": "硬幣機\/紙鈔機",
|
||||||
"Command Center": "指令中心",
|
"Command Center": "指令中心",
|
||||||
"Command Confirmation": "指令確認",
|
"Command Confirmation": "指令確認",
|
||||||
@ -229,7 +232,6 @@
|
|||||||
"Confirm replenishment completed?": "確定補貨已完成?",
|
"Confirm replenishment completed?": "確定補貨已完成?",
|
||||||
"Confirm this stock-in order?": "確定要確認此進貨單嗎?",
|
"Confirm this stock-in order?": "確定要確認此進貨單嗎?",
|
||||||
"Confirm this transfer?": "確定要確認此調撥作業嗎?",
|
"Confirm this transfer?": "確定要確認此調撥作業嗎?",
|
||||||
"Current Stocks": "目前庫存",
|
|
||||||
"Connected": "通訊中",
|
"Connected": "通訊中",
|
||||||
"Connecting...": "連線中",
|
"Connecting...": "連線中",
|
||||||
"Connection lost (LWT)": "連線中斷",
|
"Connection lost (LWT)": "連線中斷",
|
||||||
@ -273,12 +275,14 @@
|
|||||||
"Created By": "建立者",
|
"Created By": "建立者",
|
||||||
"Creation Time": "建立時間",
|
"Creation Time": "建立時間",
|
||||||
"Creator": "建立者",
|
"Creator": "建立者",
|
||||||
|
"Credit Card": "信用卡",
|
||||||
"Credit Card \/ Contactless": "支援信用卡 \/ 感應支付",
|
"Credit Card \/ Contactless": "支援信用卡 \/ 感應支付",
|
||||||
"Critical": "嚴重",
|
"Critical": "嚴重",
|
||||||
"Current": "當前",
|
"Current": "當前",
|
||||||
"Current Password": "當前密碼",
|
"Current Password": "當前密碼",
|
||||||
"Current Status": "當前狀態",
|
"Current Status": "當前狀態",
|
||||||
"Current Stock": "當前庫存",
|
"Current Stock": "當前庫存",
|
||||||
|
"Current Stocks": "目前庫存",
|
||||||
"Current Type": "當前類型",
|
"Current Type": "當前類型",
|
||||||
"Current:": "現:",
|
"Current:": "現:",
|
||||||
"Customer Details": "客戶詳情",
|
"Customer Details": "客戶詳情",
|
||||||
@ -350,7 +354,9 @@
|
|||||||
"Draft": "草稿",
|
"Draft": "草稿",
|
||||||
"Duration": "時長",
|
"Duration": "時長",
|
||||||
"Duration (Seconds)": "播放秒數",
|
"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": "玉山銀行標籤支付",
|
||||||
"E.SUN QR Scan Settings Description": "玉山銀行掃碼支付設定",
|
"E.SUN QR Scan Settings Description": "玉山銀行掃碼支付設定",
|
||||||
"EASY_MERCHANT_ID": "悠遊付 商店代號",
|
"EASY_MERCHANT_ID": "悠遊付 商店代號",
|
||||||
@ -437,10 +443,11 @@
|
|||||||
"Failed to update machine images: ": "更新機台圖片失敗:",
|
"Failed to update machine images: ": "更新機台圖片失敗:",
|
||||||
"Feature Settings": "功能設定",
|
"Feature Settings": "功能設定",
|
||||||
"Feature Toggles": "功能開關",
|
"Feature Toggles": "功能開關",
|
||||||
"Filter": "篩選",
|
|
||||||
"Fill Rate": "滿載率",
|
"Fill Rate": "滿載率",
|
||||||
"Fill in the device repair or maintenance details": "填寫設備維修或保養詳情",
|
"Fill in the device repair or maintenance details": "填寫設備維修或保養詳情",
|
||||||
"Fill in the product details below": "請在下方填寫商品的詳細資訊",
|
"Fill in the product details below": "請在下方填寫商品的詳細資訊",
|
||||||
|
"Filter": "篩選",
|
||||||
|
"Filter by Warehouse Presence": "依據倉庫存貨篩選",
|
||||||
"Firmware Version": "韌體版本",
|
"Firmware Version": "韌體版本",
|
||||||
"Firmware updated to :version": "韌體版本更新 : :version",
|
"Firmware updated to :version": "韌體版本更新 : :version",
|
||||||
"Fleet Avg OEE": "全機台平均 OEE",
|
"Fleet Avg OEE": "全機台平均 OEE",
|
||||||
@ -454,6 +461,7 @@
|
|||||||
"Full Access": "全機台授權",
|
"Full Access": "全機台授權",
|
||||||
"Full Name": "名稱",
|
"Full Name": "名稱",
|
||||||
"Full Points": "全點數",
|
"Full Points": "全點數",
|
||||||
|
"Functional Settings": "功能設定",
|
||||||
"Games": "互動遊戲",
|
"Games": "互動遊戲",
|
||||||
"General permissions not linked to a specific menu.": "未連結到特定選單的一般權限。",
|
"General permissions not linked to a specific menu.": "未連結到特定選單的一般權限。",
|
||||||
"Gift Definitions": "禮品設定",
|
"Gift Definitions": "禮品設定",
|
||||||
@ -483,7 +491,7 @@
|
|||||||
"Image": "圖片",
|
"Image": "圖片",
|
||||||
"Immediate": "立即",
|
"Immediate": "立即",
|
||||||
"In Stock": "當前庫存",
|
"In Stock": "當前庫存",
|
||||||
"Includes Credit Card\/Mobile Pay": "包括 \/信用卡\/卡片支付\/手機支付",
|
"Includes Credit Card\/Mobile Pay": "含信用卡\/行動支付",
|
||||||
"Indefinite": "無限期",
|
"Indefinite": "無限期",
|
||||||
"Info": "一般",
|
"Info": "一般",
|
||||||
"Initial Admin Account": "初始管理帳號",
|
"Initial Admin Account": "初始管理帳號",
|
||||||
@ -559,6 +567,7 @@
|
|||||||
"Machine Details": "機台詳情",
|
"Machine Details": "機台詳情",
|
||||||
"Machine Distribution": "機台分布",
|
"Machine Distribution": "機台分布",
|
||||||
"Machine Distribution Map": "機台分布地圖",
|
"Machine Distribution Map": "機台分布地圖",
|
||||||
|
"Visual overview of all machine locations": "所有機台位置的視覺化概覽",
|
||||||
"Machine Images": "機台照片",
|
"Machine Images": "機台照片",
|
||||||
"Machine Info": "機台資訊",
|
"Machine Info": "機台資訊",
|
||||||
"Machine Information": "機台資訊",
|
"Machine Information": "機台資訊",
|
||||||
@ -583,6 +592,7 @@
|
|||||||
"Machine Settings": "機台設定",
|
"Machine Settings": "機台設定",
|
||||||
"Machine Status": "機台狀態",
|
"Machine Status": "機台狀態",
|
||||||
"Machine Status List": "機台運行狀態列表",
|
"Machine Status List": "機台運行狀態列表",
|
||||||
|
"Machine System Settings": "機台系統設定",
|
||||||
"Machine Utilization": "機台稼動率",
|
"Machine Utilization": "機台稼動率",
|
||||||
"Machine created successfully.": "機台已成功建立。",
|
"Machine created successfully.": "機台已成功建立。",
|
||||||
"Machine images updated successfully.": "機台圖片已成功更新。",
|
"Machine images updated successfully.": "機台圖片已成功更新。",
|
||||||
@ -725,6 +735,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 roles available": "目前沒有角色資料。",
|
"No roles available": "目前沒有角色資料。",
|
||||||
"No roles found.": "找不到角色資料。",
|
"No roles found.": "找不到角色資料。",
|
||||||
"No slot data": "找不到貨道資料",
|
"No slot data": "找不到貨道資料",
|
||||||
@ -871,7 +882,7 @@
|
|||||||
"Price \/ Member": "售價 \/ 會員價",
|
"Price \/ Member": "售價 \/ 會員價",
|
||||||
"Pricing Information": "價格資訊",
|
"Pricing Information": "價格資訊",
|
||||||
"Product": "商品",
|
"Product": "商品",
|
||||||
"Product / Stock": "商品 / 庫存",
|
"Product \/ Stock": "商品 \/ 庫存",
|
||||||
"Product Count": "商品數量",
|
"Product Count": "商品數量",
|
||||||
"Product Details": "商品細項",
|
"Product Details": "商品細項",
|
||||||
"Product ID": "商品編號",
|
"Product ID": "商品編號",
|
||||||
@ -1000,7 +1011,7 @@
|
|||||||
"Saved.": "已儲存",
|
"Saved.": "已儲存",
|
||||||
"Saving...": "儲存中...",
|
"Saving...": "儲存中...",
|
||||||
"Scale level and access control": "層級與存取控制",
|
"Scale level and access control": "層級與存取控制",
|
||||||
"Scan Pay": "掃碼支付",
|
"Scan Pay": "掃碼支付功能",
|
||||||
"Scan this code to quickly access the maintenance form for this device.": "掃描此 QR Code 即可快速進入此設備的維修單填寫頁面。",
|
"Scan this code to quickly access the maintenance form for this device.": "掃描此 QR Code 即可快速進入此設備的維修單填寫頁面。",
|
||||||
"Schedule": "排程區間",
|
"Schedule": "排程區間",
|
||||||
"Search Company Title...": "搜尋公司名稱...",
|
"Search Company Title...": "搜尋公司名稱...",
|
||||||
@ -1062,9 +1073,11 @@
|
|||||||
"Service Periods": "服務區間",
|
"Service Periods": "服務區間",
|
||||||
"Service Terms": "服務期程",
|
"Service Terms": "服務期程",
|
||||||
"Settings Saved": "設定已儲存",
|
"Settings Saved": "設定已儲存",
|
||||||
|
"Settings updated successfully.": "設定更新成功。",
|
||||||
"Settlement": "結帳處理",
|
"Settlement": "結帳處理",
|
||||||
"Shopping Cart": "購物車功能",
|
"Shopping Cart": "購物車",
|
||||||
"Shopping Cart Feature": "購物車功能",
|
"Shopping Cart Feature": "購物車功能",
|
||||||
|
"Shopping Cart Function": "購物車功能",
|
||||||
"Show": "顯示",
|
"Show": "顯示",
|
||||||
"Show material code field in products": "在商品資料中顯示物料編號欄位",
|
"Show material code field in products": "在商品資料中顯示物料編號欄位",
|
||||||
"Show points rules in products": "在商品資料中顯示點數規則相關欄位",
|
"Show points rules in products": "在商品資料中顯示點數規則相關欄位",
|
||||||
@ -1259,6 +1272,7 @@
|
|||||||
"Update Product": "更新商品",
|
"Update Product": "更新商品",
|
||||||
"Update Settings": "更新設定",
|
"Update Settings": "更新設定",
|
||||||
"Update existing role and permissions.": "更新現有角色與權限設定。",
|
"Update existing role and permissions.": "更新現有角色與權限設定。",
|
||||||
|
"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": "上傳圖片",
|
||||||
@ -1320,10 +1334,9 @@
|
|||||||
"Warranty Service": "保固服務",
|
"Warranty Service": "保固服務",
|
||||||
"Warranty Start": "保固起始",
|
"Warranty Start": "保固起始",
|
||||||
"Welcome Gift": "來店禮",
|
"Welcome Gift": "來店禮",
|
||||||
"Welcome Gift Feature": "來店禮功能",
|
"Welcome Gift Feature": "迎賓禮功能",
|
||||||
|
"Welcome Gift Function": "迎賓禮功能",
|
||||||
"Welcome Gift Status": "來店禮",
|
"Welcome Gift Status": "來店禮",
|
||||||
"Work Content": "工作內容",
|
|
||||||
"Yes, regenerate": "確認重新產生",
|
|
||||||
"Yesterday": "昨日",
|
"Yesterday": "昨日",
|
||||||
"You cannot assign permissions you do not possess.": "您無法指派您自身不具備的權限。",
|
"You cannot assign permissions you do not possess.": "您無法指派您自身不具備的權限。",
|
||||||
"You cannot delete your own account.": "您無法刪除自己的帳號。",
|
"You cannot delete your own account.": "您無法刪除自己的帳號。",
|
||||||
@ -1422,6 +1435,5 @@
|
|||||||
"visit_gift": "來店禮",
|
"visit_gift": "來店禮",
|
||||||
"vs Yesterday": "較昨日",
|
"vs Yesterday": "較昨日",
|
||||||
"warehouses": "倉庫管理",
|
"warehouses": "倉庫管理",
|
||||||
"Filter by Warehouse Presence": "依據倉庫存貨篩選",
|
|
||||||
"待填寫": "待填寫"
|
"待填寫": "待填寫"
|
||||||
}
|
}
|
||||||
@ -8,6 +8,61 @@
|
|||||||
modelSearch: '',
|
modelSearch: '',
|
||||||
permissionSearch: '',
|
permissionSearch: '',
|
||||||
permissionCompanyId: '{{ request('company_id') }}',
|
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,
|
showCreateMachineModal: false,
|
||||||
showPhotoModal: false,
|
showPhotoModal: false,
|
||||||
showDetailDrawer: false,
|
showDetailDrawer: false,
|
||||||
@ -25,6 +80,22 @@
|
|||||||
maintenanceQrMachineName: '',
|
maintenanceQrMachineName: '',
|
||||||
maintenanceQrUrl: '',
|
maintenanceQrUrl: '',
|
||||||
permissionSearchQuery: '',
|
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) {
|
openMaintenanceQr(machine) {
|
||||||
this.maintenanceQrMachineName = machine.name;
|
this.maintenanceQrMachineName = machine.name;
|
||||||
const baseUrl = '{{ route('admin.maintenance.create', ['serial_no' => 'SERIAL_NO']) }}';
|
const baseUrl = '{{ route('admin.maintenance.create', ['serial_no' => 'SERIAL_NO']) }}';
|
||||||
@ -197,7 +268,7 @@
|
|||||||
// === 搜尋/分頁 AJAX(僅在搜尋或換頁時觸發,Tab 切換不走此路) ===
|
// === 搜尋/分頁 AJAX(僅在搜尋或換頁時觸發,Tab 切換不走此路) ===
|
||||||
async searchInTab(tabName, extraQuery = '') {
|
async searchInTab(tabName, extraQuery = '') {
|
||||||
this.tabLoading = true;
|
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] || '';
|
const search = searchMap[tabName] || '';
|
||||||
let qs = `tab=${tabName}&_ajax=1`;
|
let qs = `tab=${tabName}&_ajax=1`;
|
||||||
if (search) qs += `&search=${encodeURIComponent(search)}`;
|
if (search) qs += `&search=${encodeURIComponent(search)}`;
|
||||||
@ -286,7 +357,7 @@
|
|||||||
});
|
});
|
||||||
// 首次載入時綁定每個 Tab 的分頁連結
|
// 首次載入時綁定每個 Tab 的分頁連結
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
['machines', 'models', 'permissions'].forEach(t => {
|
['machines', 'models', 'permissions', 'system_settings'].forEach(t => {
|
||||||
const ref = this.$refs[t + 'Content'];
|
const ref = this.$refs[t + 'Content'];
|
||||||
if (ref) this.bindPaginationLinks(ref, t);
|
if (ref) this.bindPaginationLinks(ref, t);
|
||||||
});
|
});
|
||||||
@ -294,6 +365,7 @@
|
|||||||
// Tab 切換時同步 URL
|
// Tab 切換時同步 URL
|
||||||
this.$watch('tab', (newTab) => {
|
this.$watch('tab', (newTab) => {
|
||||||
history.pushState({}, '', `{{ route('admin.basic-settings.machines.index') }}?tab=${newTab}`);
|
history.pushState({}, '', `{{ route('admin.basic-settings.machines.index') }}?tab=${newTab}`);
|
||||||
|
window.dispatchEvent(new CustomEvent('tab-changed', { detail: { tab: newTab } }));
|
||||||
});
|
});
|
||||||
// 瀏覽器上一頁/下一頁
|
// 瀏覽器上一頁/下一頁
|
||||||
window.addEventListener('popstate', () => {
|
window.addEventListener('popstate', () => {
|
||||||
@ -302,19 +374,209 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}" @execute-regenerate.window="executeRegeneration($event.detail)">
|
}" @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 -->
|
<!-- 1. Header Area -->
|
||||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||||
<div>
|
<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>
|
<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>
|
<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>
|
||||||
<div class="flex items-center gap-3">
|
<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">
|
<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">
|
<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" />
|
<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">
|
class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest transition-all">
|
||||||
{{ __('Machine Permissions') }}
|
{{ __('Machine Permissions') }}
|
||||||
</button>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- 2. Main Content Card -->
|
<!-- 2. Main Content Card -->
|
||||||
@ -393,6 +660,12 @@
|
|||||||
@include('admin.basic-settings.machines.partials.tab-permissions')
|
@include('admin.basic-settings.machines.partials.tab-permissions')
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</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: '',
|
note: '',
|
||||||
settings: {
|
settings: {
|
||||||
enable_material_code: false,
|
enable_material_code: false,
|
||||||
enable_points: 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
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openCreateModal() {
|
openCreateModal() {
|
||||||
@ -49,14 +42,7 @@
|
|||||||
status: 1, note: '',
|
status: 1, note: '',
|
||||||
settings: {
|
settings: {
|
||||||
enable_material_code: false,
|
enable_material_code: false,
|
||||||
enable_points: 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
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.showModal = true;
|
this.showModal = true;
|
||||||
@ -73,14 +59,7 @@
|
|||||||
software_end_date: company.software_end_date ? company.software_end_date.substring(0, 10) : '',
|
software_end_date: company.software_end_date ? company.software_end_date.substring(0, 10) : '',
|
||||||
settings: {
|
settings: {
|
||||||
enable_material_code: company.settings?.enable_material_code || false,
|
enable_material_code: company.settings?.enable_material_code || false,
|
||||||
enable_points: company.settings?.enable_points || 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
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.originalStatus = company.status;
|
this.originalStatus = company.status;
|
||||||
@ -109,14 +88,7 @@
|
|||||||
...company,
|
...company,
|
||||||
settings: {
|
settings: {
|
||||||
enable_material_code: company.settings?.enable_material_code || false,
|
enable_material_code: company.settings?.enable_material_code || false,
|
||||||
enable_points: company.settings?.enable_points || 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
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.sidebarView = 'detail';
|
this.sidebarView = 'detail';
|
||||||
@ -127,14 +99,7 @@
|
|||||||
...company,
|
...company,
|
||||||
settings: {
|
settings: {
|
||||||
enable_material_code: company.settings?.enable_material_code || false,
|
enable_material_code: company.settings?.enable_material_code || false,
|
||||||
enable_points: company.settings?.enable_points || 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
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.sidebarView = 'history';
|
this.sidebarView = 'history';
|
||||||
@ -148,14 +113,7 @@
|
|||||||
...company,
|
...company,
|
||||||
settings: {
|
settings: {
|
||||||
enable_material_code: company.settings?.enable_material_code || false,
|
enable_material_code: company.settings?.enable_material_code || false,
|
||||||
enable_points: company.settings?.enable_points || 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
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.showHistoryModal = true;
|
this.showHistoryModal = true;
|
||||||
@ -165,14 +123,7 @@
|
|||||||
...company,
|
...company,
|
||||||
settings: {
|
settings: {
|
||||||
enable_material_code: company.settings?.enable_material_code === true || company.settings?.enable_material_code === 1 || company.settings?.enable_material_code === '1',
|
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',
|
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',
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.showSettingsModal = true;
|
this.showSettingsModal = true;
|
||||||
@ -221,11 +172,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}">
|
}">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display transition-all duration-300">
|
<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>
|
</h1>
|
||||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">
|
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">
|
||||||
{{ __('Manage all tenant accounts and validity') }}
|
{{ __('Manage all tenant accounts and validity') }}
|
||||||
@ -603,27 +555,6 @@
|
|||||||
@if($settings['enable_points'] ?? false)
|
@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>
|
<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
|
@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
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -1012,116 +943,6 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
<div class="flex justify-end gap-x-4 pt-8 mt-6 border-t border-slate-100 dark:border-slate-800">
|
<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>
|
<span x-text="detailCompany.settings?.enable_points ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Electronic Invoice -->
|
<!-- Electronic Invoice, Card System, etc. moved to Machine settings -->
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@ -56,16 +56,16 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.$watch('activeTab', (newTab, oldTab) => {
|
this.$watch('activeTab', (newTab) => {
|
||||||
const container = document.getElementById('tab-' + newTab + '-container');
|
|
||||||
if (container && container.innerHTML.trim() === '') {
|
|
||||||
this.fetchTabData(newTab);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update URL
|
// Update URL
|
||||||
const url = new URL(window.location.origin + window.location.pathname);
|
const url = new URL(window.location.origin + window.location.pathname);
|
||||||
url.searchParams.set('tab', newTab);
|
url.searchParams.set('tab', newTab);
|
||||||
window.history.pushState({}, '', url);
|
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) {
|
if (form) {
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
formData.forEach((value, key) => {
|
formData.forEach((value, key) => {
|
||||||
if (value.trim() !== '') params.set(key, value);
|
if (value.trim() !== '') params.append(key, value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
url = `${window.location.pathname}?${params.toString()}`;
|
url = `${window.location.pathname}?${params.toString()}`;
|
||||||
@ -185,6 +185,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleFilterSubmit(tab) {
|
handleFilterSubmit(tab) {
|
||||||
|
console.log('handleFilterSubmit called for tab:', tab);
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.delete('page');
|
url.searchParams.delete('page');
|
||||||
this.fetchTabData(tab);
|
this.fetchTabData(tab);
|
||||||
@ -242,7 +243,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<template x-if="activeTab === 'stock-in'">
|
<template x-if="activeTab === 'stock'">
|
||||||
<button type="button" @click="openStockInModal()"
|
<button type="button" @click="openStockInModal()"
|
||||||
class="btn-luxury-primary transition-all duration-300">
|
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">
|
<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"
|
<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">
|
aria-label="Tabs">
|
||||||
<button type="button" @click="activeTab = 'stock'"
|
<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">
|
class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest transition-all duration-300">
|
||||||
{{ __('Current Stocks') }}
|
{{ __('Current Stocks') }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" @click="activeTab = 'stock-in'"
|
<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">
|
class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest transition-all duration-300">
|
||||||
{{ __('Stock-In Orders') }}
|
{{ __('Stock-In Orders') }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" @click="activeTab = 'movements'"
|
<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">
|
class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest transition-all duration-300">
|
||||||
{{ __('Movement History') }}
|
{{ __('Movement History') }}
|
||||||
</button>
|
</button>
|
||||||
@ -300,7 +301,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tab-stock-container" class="relative">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -330,7 +331,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tab-stock-in-container" class="relative">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -359,7 +360,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tab-movements-container" class="relative">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -445,7 +446,7 @@
|
|||||||
<label class="block text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Product') }}</label>
|
<label class="block text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Product') }}</label>
|
||||||
<x-searchable-select
|
<x-searchable-select
|
||||||
x-bind:name="'items['+index+'][product_id]'"
|
x-bind:name="'items['+index+'][product_id]'"
|
||||||
:options="$products ?? []"
|
:options="$stock_in_products ?? []"
|
||||||
placeholder="{{ __('Select Product') }}"
|
placeholder="{{ __('Select Product') }}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -554,33 +555,33 @@
|
|||||||
<template x-if="activeOrder">
|
<template x-if="activeOrder">
|
||||||
<div class="space-y-10">
|
<div class="space-y-10">
|
||||||
<!-- Info Grid -->
|
<!-- 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="grid grid-cols-2 gap-8 p-8 bg-slate-50/50 dark:bg-slate-800/30 rounded-[2rem] border border-slate-100 dark:border-slate-800">
|
||||||
<div class="space-y-1">
|
<div class="space-y-2">
|
||||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Status') }}</p>
|
<p class="text-xs font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Status') }}</p>
|
||||||
<div>
|
<div>
|
||||||
<template x-if="activeOrder.status === 'draft'">
|
<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>
|
||||||
<template x-if="activeOrder.status === 'completed'">
|
<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>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1">
|
<div class="space-y-2">
|
||||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Created By') }}</p>
|
<p class="text-xs 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>
|
<p class="text-sm font-bold text-slate-700 dark:text-slate-300" x-text="activeOrder.creator_name"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1">
|
<div class="space-y-2">
|
||||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Created At') }}</p>
|
<p class="text-xs 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>
|
<p class="text-sm font-mono font-bold text-slate-600 dark:text-slate-400" x-text="activeOrder.created_at"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1">
|
<div class="space-y-2">
|
||||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Approved At') }}</p>
|
<p class="text-xs 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>
|
<p class="text-sm font-mono font-bold text-slate-600 dark:text-slate-400" x-text="activeOrder.completed_at || '-'"></p>
|
||||||
</div>
|
</div>
|
||||||
<template x-if="activeOrder.note">
|
<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">
|
<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>
|
<p class="text-xs font-bold text-slate-600 dark:text-slate-400 italic" x-text="activeOrder.note"></p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
@submit.prevent="handleFilterSubmit('movements')">
|
@submit.prevent="handleFilterSubmit('movements')">
|
||||||
<input type="hidden" name="tab" value="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">
|
<div class="w-64">
|
||||||
<x-searchable-select
|
<x-searchable-select
|
||||||
name="warehouse_id"
|
name="warehouse_id"
|
||||||
@ -28,12 +28,40 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</x-searchable-select>
|
</x-searchable-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
{{-- Date Range --}}
|
||||||
<button type="button" @click="handleFilterSubmit('movements')" class="btn-luxury-secondary px-6">
|
<div class="relative group w-72"
|
||||||
{{ __('Filter') }}
|
x-data="{ fp: null }"
|
||||||
</button>
|
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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@ -54,13 +82,13 @@
|
|||||||
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
||||||
<td class="px-6 py-5">
|
<td class="px-6 py-5">
|
||||||
<div class="flex items-center gap-x-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-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">
|
<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" />
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<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 }}
|
{{ $mv->product?->name }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex items-center gap-2 mt-0.5">
|
<div class="flex items-center gap-2 mt-0.5">
|
||||||
@ -77,18 +105,18 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-5 text-center whitespace-nowrap">
|
<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>
|
<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>
|
||||||
<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">
|
<div class="flex flex-col items-center">
|
||||||
<span class="text-lg tracking-tighter">{{ in_array($mv->type, ['in', 'transfer_in']) ? '+' : '-' }}{{ $mv->quantity }}</span>
|
<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 class="text-[10px] font-bold text-slate-400 dark:text-slate-500 tracking-tight">({{ $mv->before_qty }} → {{ $mv->after_qty }})</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-5 text-center whitespace-nowrap">
|
<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">
|
<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') }}
|
{{ $mv->created_at?->format('Y-m-d H:i:s') }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-5 text-right whitespace-nowrap">
|
<td class="px-6 py-5 text-right whitespace-nowrap">
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||||
{{-- Filters --}}
|
{{-- 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="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')">
|
@submit.prevent="handleFilterSubmit('stock-in')">
|
||||||
<input type="hidden" name="tab" value="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">
|
<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"
|
<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">
|
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
@ -11,17 +13,62 @@
|
|||||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<input type="text" name="search" value="{{ request('search') }}"
|
<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...') }}">
|
class="py-2.5 pl-12 pr-6 block w-64 luxury-input" placeholder="{{ __('Search order number...') }}">
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
{{-- Date Range --}}
|
||||||
<button type="button" @click="handleFilterSubmit('stock-in')" class="btn-luxury-secondary px-6">
|
<div class="relative group w-72"
|
||||||
{{ __('Filter') }}
|
x-data="{ fp: null }"
|
||||||
</button>
|
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>
|
</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 --}}
|
{{-- Table --}}
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||||
@ -85,8 +132,8 @@
|
|||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-5 text-center whitespace-nowrap">
|
<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">
|
<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') }}
|
{{ $order->created_at->format('Y-m-d H:i:s') }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-5 text-right whitespace-nowrap">
|
<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">
|
<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"
|
<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">
|
<input type="hidden" name="tab" value="stock">
|
||||||
<div class="relative group">
|
<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>
|
<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>
|
||||||
<div class="min-w-[240px]">
|
|
||||||
<x-searchable-select
|
{{-- 多選倉庫篩選器 --}}
|
||||||
name="warehouse_id"
|
<div class="min-w-[260px]">
|
||||||
|
<x-multi-select
|
||||||
|
name="warehouse_ids"
|
||||||
:options="$warehouses"
|
:options="$warehouses"
|
||||||
:selected="request('warehouse_id')"
|
:selected="request('warehouse_ids', [])"
|
||||||
:placeholder="__('Filter by Warehouse Presence')"
|
:placeholder="__('Filter by Warehouse Presence')"
|
||||||
@change="handleFilterSubmit('stock')"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -21,37 +26,49 @@
|
|||||||
<table class="w-full text-left border-separate border-spacing-y-0 min-w-max">
|
<table class="w-full text-left border-separate border-spacing-y-0 min-w-max">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-slate-50/50 dark:bg-white/[0.02]">
|
<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)
|
@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">
|
<div class="flex flex-col items-center">
|
||||||
<span class="text-slate-800 dark:text-slate-200">{{ $w->name }}</span>
|
<span class="text-base font-black text-slate-800 dark:text-slate-200 tracking-tight">{{ $w->name }}</span>
|
||||||
<span class="text-[9px] opacity-60 tracking-normal mt-0.5">{{ $w->type === 'main' ? __('Main') : __('Branch') }}</span>
|
<span class="text-[10px] font-bold opacity-60 tracking-widest mt-0.5 uppercase">{{ $w->type === 'main' ? __('Main') : __('Branch') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
@endforeach
|
@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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-slate-50 dark:divide-white/[0.05]">
|
<tbody class="divide-y divide-slate-50 dark:divide-white/[0.05]">
|
||||||
@forelse($products as $product)
|
@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">
|
<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)]">
|
<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-4">
|
<div class="flex items-center gap-3 md: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">
|
<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)
|
@if($product->image_url)
|
||||||
<img src="{{ $product->image_url }}" class="w-full h-full object-cover">
|
<img src="{{ $product->image_url }}" class="w-full h-full object-cover">
|
||||||
@else
|
@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
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col min-w-0">
|
||||||
<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-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>
|
||||||
<span class="text-[10px] font-bold text-slate-400 mt-0.5 tracking-tight">{{ $product->barcode ?? '-' }}</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>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</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)
|
@foreach($inventory_warehouses as $w)
|
||||||
@php
|
@php
|
||||||
$stock = $product->stocks->firstWhere('warehouse_id', $w->id);
|
$stock = $product->stocks->firstWhere('warehouse_id', $w->id);
|
||||||
@ -72,11 +89,6 @@
|
|||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
@endforeach
|
@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>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@ -89,6 +89,12 @@
|
|||||||
'machine-models' => __('Machine Models'),
|
'machine-models' => __('Machine Models'),
|
||||||
default => null,
|
default => null,
|
||||||
},
|
},
|
||||||
|
'permission' => match($segments[2] ?? '') {
|
||||||
|
'companies' => __('Customer Management'),
|
||||||
|
'accounts' => __('Account Management'),
|
||||||
|
'roles' => __('Roles'),
|
||||||
|
default => null,
|
||||||
|
},
|
||||||
default => null,
|
default => null,
|
||||||
},
|
},
|
||||||
'edit' => str_starts_with($routeName, 'profile') ? __('Profile') : __('Edit'),
|
'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::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('/', [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::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)
|
// 地址轉座標 (Geocoding Proxy)
|
||||||
Route::post('/geocode', [App\Http\Controllers\Admin\GeocodingController::class, 'resolve'])->name('geocode');
|
Route::post('/geocode', [App\Http\Controllers\Admin\GeocodingController::class, 'resolve'])->name('geocode');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user