[PROMOTE] demo -> main
This commit is contained in:
commit
c6ce58f14a
@ -95,6 +95,10 @@ class ListenMqttQueue extends Command
|
|||||||
$finalPayload = is_array($payload) ? $payload : ['raw_data' => $payload];
|
$finalPayload = is_array($payload) ? $payload : ['raw_data' => $payload];
|
||||||
ProcessHeartbeat::dispatch($serialNo, $finalPayload);
|
ProcessHeartbeat::dispatch($serialNo, $finalPayload);
|
||||||
break;
|
break;
|
||||||
|
case 'ambient_temp':
|
||||||
|
$finalPayload = is_array($payload) ? $payload : ['temperature' => $payload];
|
||||||
|
\App\Jobs\Machine\ProcessAmbientTemp::dispatch($serialNo, $finalPayload);
|
||||||
|
break;
|
||||||
case 'status':
|
case 'status':
|
||||||
ProcessStatus::dispatch($serialNo, $payload);
|
ProcessStatus::dispatch($serialNo, $payload);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -213,6 +213,7 @@ class MachineSettingController extends AdminController
|
|||||||
'scan_pay_linepay_enabled' => 'boolean',
|
'scan_pay_linepay_enabled' => 'boolean',
|
||||||
'shopping_cart_enabled' => 'boolean',
|
'shopping_cart_enabled' => 'boolean',
|
||||||
'cash_module_enabled' => 'boolean',
|
'cash_module_enabled' => 'boolean',
|
||||||
|
'ambient_temp_monitoring_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',
|
||||||
@ -333,6 +334,7 @@ class MachineSettingController extends AdminController
|
|||||||
'welcome_gift_enabled',
|
'welcome_gift_enabled',
|
||||||
'cash_module_enabled',
|
'cash_module_enabled',
|
||||||
'member_system_enabled',
|
'member_system_enabled',
|
||||||
|
'ambient_temp_monitoring_enabled',
|
||||||
];
|
];
|
||||||
$data = [];
|
$data = [];
|
||||||
foreach ($allowedFields as $field) {
|
foreach ($allowedFields as $field) {
|
||||||
@ -361,6 +363,7 @@ class MachineSettingController extends AdminController
|
|||||||
'welcome_gift_enabled',
|
'welcome_gift_enabled',
|
||||||
'cash_module_enabled',
|
'cash_module_enabled',
|
||||||
'member_system_enabled',
|
'member_system_enabled',
|
||||||
|
'ambient_temp_monitoring_enabled',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!in_array($field, $allowedFields)) {
|
if (!in_array($field, $allowedFields)) {
|
||||||
|
|||||||
@ -43,10 +43,48 @@ class MachineController extends AdminController
|
|||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
|
'ambient_temp_setting' => 'nullable|integer',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$machine->update($validated);
|
$machine->update($validated);
|
||||||
|
|
||||||
|
if ($machine->wasChanged('ambient_temp_setting')) {
|
||||||
|
$newSetting = $machine->ambient_temp_setting;
|
||||||
|
|
||||||
|
// 將先前未執行的同類型指令標記為 superseded (被取代)
|
||||||
|
\App\Models\Machine\RemoteCommand::where('machine_id', $machine->id)
|
||||||
|
->where('command_type', 'ambient_temp_limit')
|
||||||
|
->where('status', 'pending')
|
||||||
|
->update([
|
||||||
|
'status' => 'superseded',
|
||||||
|
'remark' => __('Superseded by new command'),
|
||||||
|
'executed_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 建立新指令
|
||||||
|
$command = \App\Models\Machine\RemoteCommand::create([
|
||||||
|
'machine_id' => $machine->id,
|
||||||
|
'user_id' => auth()->id() ?: 1,
|
||||||
|
'command_type' => 'ambient_temp_limit',
|
||||||
|
'payload' => [
|
||||||
|
'temperature' => $newSetting !== null ? (int)$newSetting : null
|
||||||
|
],
|
||||||
|
'status' => 'pending',
|
||||||
|
'remark' => $newSetting !== null
|
||||||
|
? __('Temperature reaches :temp degrees, the fan will turn on', ['temp' => $newSetting])
|
||||||
|
: __('Disabled ambient temperature monitoring')
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 發送 MQTT 指令
|
||||||
|
$mqttService = app(\App\Services\Machine\MqttService::class);
|
||||||
|
$mqttService->pushCommand(
|
||||||
|
$machine->serial_no,
|
||||||
|
$command->command_type,
|
||||||
|
$command->payload,
|
||||||
|
(string)$command->id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('admin.machines.index')
|
return redirect()->route('admin.machines.index')
|
||||||
->with('success', __('Machine updated successfully.'));
|
->with('success', __('Machine updated successfully.'));
|
||||||
}
|
}
|
||||||
@ -267,4 +305,35 @@ class MachineController extends AdminController
|
|||||||
'data' => $chartData
|
'data' => $chartData
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX: 取得機台環境溫度歷史紀錄 (供圖表使用)
|
||||||
|
*/
|
||||||
|
public function ambientTemperatureAjax(Request $request, Machine $machine)
|
||||||
|
{
|
||||||
|
$startDate = $request->get('start_date');
|
||||||
|
$endDate = $request->get('end_date');
|
||||||
|
|
||||||
|
$logs = $machine->logs()
|
||||||
|
->where('type', 'ambient_temp')
|
||||||
|
->where('message', 'Ambient temperature reported: :temp°C')
|
||||||
|
->when($startDate, function ($query, $start) {
|
||||||
|
return $query->where('created_at', '>=', str_replace('T', ' ', $start));
|
||||||
|
})
|
||||||
|
->when($endDate, function ($query, $end) {
|
||||||
|
return $query->where('created_at', '<=', str_replace('T', ' ', $end));
|
||||||
|
})
|
||||||
|
->oldest()
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$chartData = $logs->map(fn($log) => [
|
||||||
|
'x' => $log->created_at->getTimestamp() * 1000,
|
||||||
|
'y' => (int) ($log->context['temp'] ?? 0),
|
||||||
|
])->values()->toArray();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => $chartData
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -139,7 +139,7 @@ class RemoteController extends Controller
|
|||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'machine_id' => 'required|exists:machines,id',
|
'machine_id' => 'required|exists:machines,id',
|
||||||
'command_type' => 'required|string|in:reboot,reboot_card,checkout,lock,unlock,change,dispense',
|
'command_type' => 'required|string|in:reboot,reboot_card,checkout,lock,unlock,change,dispense,fanon,fanoff,fanauto',
|
||||||
'amount' => 'nullable|integer|min:0',
|
'amount' => 'nullable|integer|min:0',
|
||||||
'slot_no' => 'nullable|string',
|
'slot_no' => 'nullable|string',
|
||||||
'note' => 'nullable|string|max:255',
|
'note' => 'nullable|string|max:255',
|
||||||
|
|||||||
94
app/Jobs/Machine/ProcessAmbientTemp.php
Normal file
94
app/Jobs/Machine/ProcessAmbientTemp.php
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\Machine;
|
||||||
|
|
||||||
|
use App\Models\Machine\Machine;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class ProcessAmbientTemp implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $serialNo;
|
||||||
|
protected $payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct(string $serialNo, $payload)
|
||||||
|
{
|
||||||
|
$this->serialNo = $serialNo;
|
||||||
|
$this->payload = (array) $payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$machine = Machine::withoutGlobalScopes()->where('serial_no', $this->serialNo)->first();
|
||||||
|
|
||||||
|
if (!$machine) {
|
||||||
|
Log::warning("MQTT Ambient Temp: Machine not found", ['serial_no' => $this->serialNo]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 環境溫度欄位更新
|
||||||
|
// 支持機台傳送 payload 為 {"temperature": 25} 或 {"ambient_temp": 25} 等結構
|
||||||
|
$temp = null;
|
||||||
|
if (isset($this->payload['temperature'])) {
|
||||||
|
$temp = (int) $this->payload['temperature'];
|
||||||
|
} elseif (isset($this->payload['ambient_temp'])) {
|
||||||
|
$temp = (int) $this->payload['ambient_temp'];
|
||||||
|
} elseif (isset($this->payload['raw_data'])) {
|
||||||
|
$temp = (int) $this->payload['raw_data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($temp === null) {
|
||||||
|
Log::warning("MQTT Ambient Temp: Missing temperature field in payload", [
|
||||||
|
'serial_no' => $this->serialNo,
|
||||||
|
'payload' => $this->payload
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug("ProcessAmbientTemp: Ambient temperature reported for {$this->serialNo}: {$temp}");
|
||||||
|
|
||||||
|
$updateData = [
|
||||||
|
'ambient_temperature' => $temp,
|
||||||
|
];
|
||||||
|
|
||||||
|
// 讀取最後一次記錄日誌的溫度與時間,避免頻繁寫入相同的溫度日誌
|
||||||
|
$tempLogCacheKey = "machine:{$this->serialNo}:last_ambient_temp_log";
|
||||||
|
$lastLogEntry = Cache::get($tempLogCacheKey);
|
||||||
|
|
||||||
|
// 優先從快取取值,若無則從資料庫取
|
||||||
|
$oldTemp = isset($lastLogEntry['value']) ? (int)$lastLogEntry['value'] : $machine->ambient_temperature;
|
||||||
|
|
||||||
|
if ($temp !== (int)$oldTemp) {
|
||||||
|
Log::debug("ProcessAmbientTemp: Ambient temperature changed from {$oldTemp} to {$temp}. Triggering log.");
|
||||||
|
\App\Jobs\Machine\ProcessStateLog::dispatch(
|
||||||
|
$machine->id,
|
||||||
|
$machine->company_id,
|
||||||
|
"Ambient temperature reported: :temp°C",
|
||||||
|
'info',
|
||||||
|
['temp' => $temp],
|
||||||
|
'ambient_temp' // type 欄位
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新快取 (存活時間為 7 天)
|
||||||
|
Cache::put($tempLogCacheKey, [
|
||||||
|
'value' => $temp,
|
||||||
|
'at' => now()->toDateTimeString()
|
||||||
|
], 604800);
|
||||||
|
|
||||||
|
$machine->update($updateData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -45,10 +45,10 @@ class ProcessMachineEvent implements ShouldQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 寫入機台狀態日誌,type 為 'status' 以便正確歸類顯示於「機台狀態」日誌中
|
// 寫入環境溫度日誌,type 為 'ambient_temp' 以便正確歸類顯示於「環境溫度回傳」日誌中
|
||||||
$machine->logs()->create([
|
$machine->logs()->create([
|
||||||
'company_id' => $machine->company_id,
|
'company_id' => $machine->company_id,
|
||||||
'type' => 'status', // 忠實呈現於機台狀態中
|
'type' => 'ambient_temp', // 歸類至環境溫度回傳
|
||||||
'level' => 'info',
|
'level' => 'info',
|
||||||
'message' => $event, // 例如 "fanon" 或 "fanoff"
|
'message' => $event, // 例如 "fanon" 或 "fanoff"
|
||||||
'context' => $this->payload,
|
'context' => $this->payload,
|
||||||
|
|||||||
@ -70,6 +70,9 @@ class Machine extends Model
|
|||||||
'current_page',
|
'current_page',
|
||||||
'door_status',
|
'door_status',
|
||||||
'temperature',
|
'temperature',
|
||||||
|
'ambient_temperature',
|
||||||
|
'ambient_temp_setting',
|
||||||
|
'ambient_temp_monitoring_enabled',
|
||||||
'firmware_version',
|
'firmware_version',
|
||||||
'api_token',
|
'api_token',
|
||||||
'last_heartbeat_at',
|
'last_heartbeat_at',
|
||||||
@ -309,6 +312,9 @@ class Machine extends Model
|
|||||||
protected $casts = [
|
protected $casts = [
|
||||||
'last_heartbeat_at' => 'datetime',
|
'last_heartbeat_at' => 'datetime',
|
||||||
'temperature' => 'integer',
|
'temperature' => 'integer',
|
||||||
|
'ambient_temperature' => 'integer',
|
||||||
|
'ambient_temp_setting' => 'integer',
|
||||||
|
'ambient_temp_monitoring_enabled' => 'boolean',
|
||||||
'welcome_gift_enabled' => 'boolean',
|
'welcome_gift_enabled' => 'boolean',
|
||||||
'is_spring_slot_1_10' => 'boolean',
|
'is_spring_slot_1_10' => 'boolean',
|
||||||
'is_spring_slot_11_20' => 'boolean',
|
'is_spring_slot_11_20' => 'boolean',
|
||||||
|
|||||||
@ -540,6 +540,22 @@ return [
|
|||||||
'status' => 'online'
|
'status' => 'online'
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'name' => '機台環境溫度上報 (Ambient Temperature Report)',
|
||||||
|
'slug' => 'mqtt-ambient-temp-report',
|
||||||
|
'action' => 'PUB',
|
||||||
|
'topic' => 'machine/{serial_no}/ambient_temp',
|
||||||
|
'qos' => 1,
|
||||||
|
'description' => '機台主動上報當前環境溫度。支援 temperature 或 ambient_temp 欄位上報。',
|
||||||
|
'payload_parameters' => [
|
||||||
|
'temperature' => ['type' => 'integer', 'description' => '當前環境溫度值 (例如 28)'],
|
||||||
|
'ambient_temp' => ['type' => 'integer', 'description' => '當前環境溫度值 (相容相應硬體參數)'],
|
||||||
|
],
|
||||||
|
'payload_example' => [
|
||||||
|
'temperature' => 28
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'name' => '機台硬體事件上報 (Event)',
|
'name' => '機台硬體事件上報 (Event)',
|
||||||
'slug' => 'mqtt-event',
|
'slug' => 'mqtt-event',
|
||||||
@ -773,7 +789,7 @@ return [
|
|||||||
'qos' => 1,
|
'qos' => 1,
|
||||||
'description' => '雲端主動下發的系統級別控制指令。包含重啟、設備鎖定解鎖、購物車結帳等無額外參數的指令。',
|
'description' => '雲端主動下發的系統級別控制指令。包含重啟、設備鎖定解鎖、購物車結帳等無額外參數的指令。',
|
||||||
'payload_parameters' => [
|
'payload_parameters' => [
|
||||||
'command' => ['type' => 'string', 'description' => '系統指令類型。支援:reboot (重啟機台), reboot_card (重啟刷卡機), checkout (購物車結帳), lock (設備鎖定), unlock (設備解鎖), update_ads (廣告同步), update_products (商品資料同步)'],
|
'command' => ['type' => 'string', 'description' => '系統指令類型。支援:reboot (重啟機台), reboot_card (重啟刷卡機), checkout (購物車結帳), lock (設備鎖定), unlock (設備解鎖), update_ads (廣告同步), update_products (商品資料同步), fanon (開啟風扇), fanoff (關閉風扇), fanauto (風扇自動控制)'],
|
||||||
'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'],
|
'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'],
|
||||||
'payload' => ['type' => 'object', 'description' => '空物件 (無額外參數)'],
|
'payload' => ['type' => 'object', 'description' => '空物件 (無額外參數)'],
|
||||||
],
|
],
|
||||||
@ -784,6 +800,28 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
'name' => '指令下發:環境溫度上限 (Ambient Temperature Upper Limit)',
|
||||||
|
'slug' => 'mqtt-command-ambient-temp-limit',
|
||||||
|
'action' => 'SUB',
|
||||||
|
'topic' => 'machine/{serial_no}/command',
|
||||||
|
'qos' => 1,
|
||||||
|
'description' => '雲端主動下發「環境溫度上限」指令。指示機台更新環境溫度開啟風扇的閾值設定。',
|
||||||
|
'payload_parameters' => [
|
||||||
|
'command' => ['type' => 'string', 'description' => '固定為 "ambient_temp_limit"'],
|
||||||
|
'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'],
|
||||||
|
'payload' => ['type' => 'object', 'description' => '溫度設定參數'],
|
||||||
|
'payload.temperature' => ['type' => 'integer', 'description' => '環境溫度上限閾值(可為 null 表示停用監測)'],
|
||||||
|
],
|
||||||
|
'payload_example' => [
|
||||||
|
'command' => 'ambient_temp_limit',
|
||||||
|
'command_id' => '1122334455',
|
||||||
|
'payload' => [
|
||||||
|
'temperature' => 28
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'name' => '指令執行回報 (Machine to Cloud)',
|
'name' => '指令執行回報 (Machine to Cloud)',
|
||||||
'slug' => 'mqtt-command-ack',
|
'slug' => 'mqtt-command-ack',
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
<?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) {
|
||||||
|
if (!Schema::hasColumn('machines', 'ambient_temperature')) {
|
||||||
|
$table->integer('ambient_temperature')->nullable()->after('temperature')->comment('環境溫度');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('machines', 'ambient_temp_setting')) {
|
||||||
|
$table->integer('ambient_temp_setting')->nullable()->after('ambient_temperature')->comment('環境溫度設定值');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('machines', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['ambient_temperature', 'ambient_temp_setting']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
<?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) {
|
||||||
|
if (!Schema::hasColumn('machines', 'ambient_temp_monitoring_enabled')) {
|
||||||
|
$table->boolean('ambient_temp_monitoring_enabled')->default(false)->after('ambient_temp_setting')->comment('是否啟用環境溫度監測');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('machines', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('ambient_temp_monitoring_enabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -58,8 +58,6 @@ class RoleSeeder extends Seeder
|
|||||||
'menu.reservation',
|
'menu.reservation',
|
||||||
'menu.special-permission',
|
'menu.special-permission',
|
||||||
'menu.special-permission.clear-stock',
|
'menu.special-permission.clear-stock',
|
||||||
'menu.special-permission.apk-versions',
|
|
||||||
'menu.special-permission.discord-notifications',
|
|
||||||
'menu.basic',
|
'menu.basic',
|
||||||
'menu.basic.machines',
|
'menu.basic.machines',
|
||||||
'menu.basic.payment-configs',
|
'menu.basic.payment-configs',
|
||||||
@ -123,10 +121,6 @@ class RoleSeeder extends Seeder
|
|||||||
'menu.reservation',
|
'menu.reservation',
|
||||||
'menu.special-permission',
|
'menu.special-permission',
|
||||||
'menu.special-permission.clear-stock',
|
'menu.special-permission.clear-stock',
|
||||||
'menu.special-permission.apk-versions',
|
|
||||||
'menu.special-permission.discord-notifications',
|
|
||||||
'menu.basic.discord-notifications',
|
|
||||||
'menu.basic.apk-versions',
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
20
lang/en.json
20
lang/en.json
@ -120,6 +120,13 @@
|
|||||||
"All Warehouses": "All Warehouses",
|
"All Warehouses": "All Warehouses",
|
||||||
"All issues marked as resolved.": "All issues marked as resolved.",
|
"All issues marked as resolved.": "All issues marked as resolved.",
|
||||||
"All slots are fully stocked": "All slots are fully stocked",
|
"All slots are fully stocked": "All slots are fully stocked",
|
||||||
|
"Ambient Temperature": "Ambient Temperature",
|
||||||
|
"Ambient Temperature Log": "Ambient Temperature Log",
|
||||||
|
"Ambient Temperature Monitoring": "Ambient Temperature Monitoring",
|
||||||
|
"Ambient Temperature Setting": "Ambient Temperature Setting",
|
||||||
|
"Ambient Temperature Trend": "Ambient Temperature Trend",
|
||||||
|
"Ambient Temperature Upper Limit": "Ambient Temperature Upper Limit",
|
||||||
|
"Ambient temperature reported: :temp°C": "Ambient temperature reported: :temp°C",
|
||||||
"Amount": "Amount",
|
"Amount": "Amount",
|
||||||
"Amount / Payment": "Amount / Payment",
|
"Amount / Payment": "Amount / Payment",
|
||||||
"Amount Discount": "Amount Discount",
|
"Amount Discount": "Amount Discount",
|
||||||
@ -236,6 +243,7 @@
|
|||||||
"CASH": "CASH",
|
"CASH": "CASH",
|
||||||
"CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS",
|
"CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS",
|
||||||
"Calculate Replenishment": "Calculate Replenishment",
|
"Calculate Replenishment": "Calculate Replenishment",
|
||||||
|
"Can be empty. If left blank, it means ambient temperature monitoring is disabled.": "Can be empty. If left blank, it means ambient temperature monitoring is disabled.",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
"Cancel Gift": "Cancel Gift",
|
"Cancel Gift": "Cancel Gift",
|
||||||
"Cancel Order": "Cancel Order",
|
"Cancel Order": "Cancel Order",
|
||||||
@ -511,6 +519,7 @@
|
|||||||
"Disable Product Confirmation": "Disable Product Confirmation",
|
"Disable Product Confirmation": "Disable Product Confirmation",
|
||||||
"Disable Warehouse": "Disable Warehouse",
|
"Disable Warehouse": "Disable Warehouse",
|
||||||
"Disabled": "Disabled",
|
"Disabled": "Disabled",
|
||||||
|
"Disabled ambient temperature monitoring": "Disabled ambient temperature monitoring",
|
||||||
"Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?",
|
"Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?",
|
||||||
"Discord Notifications": "Discord Notifications",
|
"Discord Notifications": "Discord Notifications",
|
||||||
"Discord Webhook URL": "Discord Webhook URL",
|
"Discord Webhook URL": "Discord Webhook URL",
|
||||||
@ -632,6 +641,7 @@
|
|||||||
"English": "English",
|
"English": "English",
|
||||||
"Ensure your account is using a long, random password to stay secure.": "Ensure your account is using a long, random password to stay secure.",
|
"Ensure your account is using a long, random password to stay secure.": "Ensure your account is using a long, random password to stay secure.",
|
||||||
"Enter ad material name": "Enter ad material name",
|
"Enter ad material name": "Enter ad material name",
|
||||||
|
"Enter ambient temperature setting...": "Enter ambient temperature setting...",
|
||||||
"Enter full address": "Enter full address",
|
"Enter full address": "Enter full address",
|
||||||
"Enter login ID": "Enter login ID",
|
"Enter login ID": "Enter login ID",
|
||||||
"Enter machine location": "Enter machine location",
|
"Enter machine location": "Enter machine location",
|
||||||
@ -690,6 +700,10 @@
|
|||||||
"Failed to send test notification.": "Failed to send test notification.",
|
"Failed to send test notification.": "Failed to send test notification.",
|
||||||
"Failed to send test notification. Please verify the Webhook URL.": "Failed to send test notification. Please verify the Webhook URL.",
|
"Failed to send test notification. Please verify the Webhook URL.": "Failed to send test notification. Please verify the Webhook URL.",
|
||||||
"Failed to update machine images: ": "Failed to update machine images: ",
|
"Failed to update machine images: ": "Failed to update machine images: ",
|
||||||
|
"Fan Auto": "Fan Auto",
|
||||||
|
"Fan Controls": "Fan Controls",
|
||||||
|
"Fan Off": "Fan Off",
|
||||||
|
"Fan On": "Fan On",
|
||||||
"Feature Settings": "Feature Settings",
|
"Feature Settings": "Feature Settings",
|
||||||
"Feature Toggles": "Feature Toggles",
|
"Feature Toggles": "Feature Toggles",
|
||||||
"Field": "Field",
|
"Field": "Field",
|
||||||
@ -822,6 +836,7 @@
|
|||||||
"Half Points Amount": "Half Points Amount",
|
"Half Points Amount": "Half Points Amount",
|
||||||
"Hardware & Network": "Hardware & Network",
|
"Hardware & Network": "Hardware & Network",
|
||||||
"Hardware & Slots": "Hardware & Slots",
|
"Hardware & Slots": "Hardware & Slots",
|
||||||
|
"Hardware Peripheral Settings": "Hardware Peripheral Settings",
|
||||||
"HashIV": "HashIV",
|
"HashIV": "HashIV",
|
||||||
"HashKey": "HashKey",
|
"HashKey": "HashKey",
|
||||||
"Heartbeat": "Heartbeat",
|
"Heartbeat": "Heartbeat",
|
||||||
@ -1955,6 +1970,7 @@
|
|||||||
"Temperature Limit": "Temperature Limit",
|
"Temperature Limit": "Temperature Limit",
|
||||||
"Temperature Restored": "Temperature Restored",
|
"Temperature Restored": "Temperature Restored",
|
||||||
"Temperature Trend": "Temperature Trend",
|
"Temperature Trend": "Temperature Trend",
|
||||||
|
"Temperature reaches :temp degrees, the fan will turn on": "Temperature reaches :temp°C, the fan will turn on",
|
||||||
"Temperature reported: :temp°C": "Temperature reported: :temp°C",
|
"Temperature reported: :temp°C": "Temperature reported: :temp°C",
|
||||||
"Temperature updated to :temp°C": "Temperature updated to :temp°C",
|
"Temperature updated to :temp°C": "Temperature updated to :temp°C",
|
||||||
"Tenant": "Tenant",
|
"Tenant": "Tenant",
|
||||||
@ -2084,6 +2100,7 @@
|
|||||||
"Update Product": "Update Product",
|
"Update Product": "Update Product",
|
||||||
"Update Settings": "Update Settings",
|
"Update Settings": "Update Settings",
|
||||||
"Update Welcome Gift": "Update Welcome Gift",
|
"Update Welcome Gift": "Update Welcome Gift",
|
||||||
|
"Update ambient temperature setting to :temp°C": "Update ambient temperature setting to :temp°C",
|
||||||
"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 failed": "Update failed",
|
||||||
"Update identification for your asset": "Update identification for your asset",
|
"Update identification for your asset": "Update identification for your asset",
|
||||||
@ -2098,6 +2115,7 @@
|
|||||||
"Upload file or provide URL": "Upload file or provide URL",
|
"Upload file or provide URL": "Upload file or provide URL",
|
||||||
"Uploading new images will replace all existing images.": "Uploading new images will replace all existing images.",
|
"Uploading new images will replace all existing images.": "Uploading new images will replace all existing images.",
|
||||||
"Uploading...": "Uploading...",
|
"Uploading...": "Uploading...",
|
||||||
|
"Upper limit; the fan will turn on when reached": "Upper limit; the fan will turn on when reached",
|
||||||
"Usage Completed": "Usage Completed",
|
"Usage Completed": "Usage Completed",
|
||||||
"Usage Limit": "Usage Limit",
|
"Usage Limit": "Usage Limit",
|
||||||
"Usage Logs": "Usage Logs",
|
"Usage Logs": "Usage Logs",
|
||||||
@ -2343,9 +2361,7 @@
|
|||||||
"menu.sales.records": "Sales Records",
|
"menu.sales.records": "Sales Records",
|
||||||
"menu.sales.store-gifts": "Store Gifts",
|
"menu.sales.store-gifts": "Store Gifts",
|
||||||
"menu.special-permission": "Special Permissions",
|
"menu.special-permission": "Special Permissions",
|
||||||
"menu.special-permission.apk-versions": "APK Versions",
|
|
||||||
"menu.special-permission.clear-stock": "Clear Stock",
|
"menu.special-permission.clear-stock": "Clear Stock",
|
||||||
"menu.special-permission.discord-notifications": "Discord Notifications",
|
|
||||||
"menu.warehouses": "Warehouse Management",
|
"menu.warehouses": "Warehouse Management",
|
||||||
"menu.warehouses.inventory": "Inventory Management",
|
"menu.warehouses.inventory": "Inventory Management",
|
||||||
"menu.warehouses.machine-inventory": "Machine Inventory Overview",
|
"menu.warehouses.machine-inventory": "Machine Inventory Overview",
|
||||||
|
|||||||
20
lang/ja.json
20
lang/ja.json
@ -120,6 +120,13 @@
|
|||||||
"All Warehouses": "すべての倉庫",
|
"All Warehouses": "すべての倉庫",
|
||||||
"All issues marked as resolved.": "すべての問題が解決済みとしてマークされました。",
|
"All issues marked as resolved.": "すべての問題が解決済みとしてマークされました。",
|
||||||
"All slots are fully stocked": "すべてのスロットが補充完了しました",
|
"All slots are fully stocked": "すべてのスロットが補充完了しました",
|
||||||
|
"Ambient Temperature": "周囲温度",
|
||||||
|
"Ambient Temperature Log": "周囲温度ログ",
|
||||||
|
"Ambient Temperature Monitoring": "環境温度監視",
|
||||||
|
"Ambient Temperature Setting": "周囲温度設定",
|
||||||
|
"Ambient Temperature Trend": "周囲温度トレンド",
|
||||||
|
"Ambient Temperature Upper Limit": "環境温度上限",
|
||||||
|
"Ambient temperature reported: :temp°C": "環境温度受信: :temp°C",
|
||||||
"Amount": "金額",
|
"Amount": "金額",
|
||||||
"Amount / Payment": "金額 / 支払い",
|
"Amount / Payment": "金額 / 支払い",
|
||||||
"Amount Discount": "金額割引",
|
"Amount Discount": "金額割引",
|
||||||
@ -236,6 +243,7 @@
|
|||||||
"CASH": "現金",
|
"CASH": "現金",
|
||||||
"CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "倉庫間の移動または機器からの返品を作成",
|
"CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "倉庫間の移動または機器からの返品を作成",
|
||||||
"Calculate Replenishment": "補充量を計算",
|
"Calculate Replenishment": "補充量を計算",
|
||||||
|
"Can be empty. If left blank, it means ambient temperature monitoring is disabled.": "空欄にすることができます。空欄の場合、周囲温度の監視は無効になります。",
|
||||||
"Cancel": "キャンセル",
|
"Cancel": "キャンセル",
|
||||||
"Cancel Gift": "ギフトをキャンセル",
|
"Cancel Gift": "ギフトをキャンセル",
|
||||||
"Cancel Order": "注文キャンセル",
|
"Cancel Order": "注文キャンセル",
|
||||||
@ -511,6 +519,7 @@
|
|||||||
"Disable Product Confirmation": "商品無効化確認",
|
"Disable Product Confirmation": "商品無効化確認",
|
||||||
"Disable Warehouse": "倉庫無効化",
|
"Disable Warehouse": "倉庫無効化",
|
||||||
"Disabled": "無効済み",
|
"Disabled": "無効済み",
|
||||||
|
"Disabled ambient temperature monitoring": "環境温度監視を停止しました",
|
||||||
"Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "顧客を無効にすると配下の全アカウントも無効になります。続行しますか?",
|
"Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "顧客を無効にすると配下の全アカウントも無効になります。続行しますか?",
|
||||||
"Discord Notifications": "Discord通知",
|
"Discord Notifications": "Discord通知",
|
||||||
"Discord Webhook URL": "Discord Webhook URL",
|
"Discord Webhook URL": "Discord Webhook URL",
|
||||||
@ -632,6 +641,7 @@
|
|||||||
"English": "英語",
|
"English": "英語",
|
||||||
"Ensure your account is using a long, random password to stay secure.": "安全を保つため、長くランダムなパスワードを使用してください。",
|
"Ensure your account is using a long, random password to stay secure.": "安全を保つため、長くランダムなパスワードを使用してください。",
|
||||||
"Enter ad material name": "広告素材名を入力",
|
"Enter ad material name": "広告素材名を入力",
|
||||||
|
"Enter ambient temperature setting...": "周囲温度設定を入力してください...",
|
||||||
"Enter full address": "完全な住所を入力",
|
"Enter full address": "完全な住所を入力",
|
||||||
"Enter login ID": "ログインIDを入力してください",
|
"Enter login ID": "ログインIDを入力してください",
|
||||||
"Enter machine location": "機器の設置場所を入力してください",
|
"Enter machine location": "機器の設置場所を入力してください",
|
||||||
@ -690,6 +700,10 @@
|
|||||||
"Failed to send test notification.": "テスト通知の送信に失敗しました。",
|
"Failed to send test notification.": "テスト通知の送信に失敗しました。",
|
||||||
"Failed to send test notification. Please verify the Webhook URL.": "テスト通知の送信に失敗しました。Webhook URLを確認してください。",
|
"Failed to send test notification. Please verify the Webhook URL.": "テスト通知の送信に失敗しました。Webhook URLを確認してください。",
|
||||||
"Failed to update machine images: ": "機器画像の更新に失敗しました: ",
|
"Failed to update machine images: ": "機器画像の更新に失敗しました: ",
|
||||||
|
"Fan Auto": "ファン自動",
|
||||||
|
"Fan Controls": "ファン制御",
|
||||||
|
"Fan Off": "ファン停止",
|
||||||
|
"Fan On": "ファン起動",
|
||||||
"Feature Settings": "機能設定",
|
"Feature Settings": "機能設定",
|
||||||
"Feature Toggles": "機能切り替え",
|
"Feature Toggles": "機能切り替え",
|
||||||
"Field": "Field",
|
"Field": "Field",
|
||||||
@ -822,6 +836,7 @@
|
|||||||
"Half Points Amount": "ハーフポイント金額",
|
"Half Points Amount": "ハーフポイント金額",
|
||||||
"Hardware & Network": "ハードウェア・ネットワーク",
|
"Hardware & Network": "ハードウェア・ネットワーク",
|
||||||
"Hardware & Slots": "ハードウェア・スロット",
|
"Hardware & Slots": "ハードウェア・スロット",
|
||||||
|
"Hardware Peripheral Settings": "ハードウェア周辺機器設定",
|
||||||
"HashIV": "HashIV",
|
"HashIV": "HashIV",
|
||||||
"HashKey": "HashKey",
|
"HashKey": "HashKey",
|
||||||
"Heartbeat": "ハートビート",
|
"Heartbeat": "ハートビート",
|
||||||
@ -1955,6 +1970,7 @@
|
|||||||
"Temperature Limit": "温度しきい値",
|
"Temperature Limit": "温度しきい値",
|
||||||
"Temperature Restored": "温度正常復帰",
|
"Temperature Restored": "温度正常復帰",
|
||||||
"Temperature Trend": "温度傾向",
|
"Temperature Trend": "温度傾向",
|
||||||
|
"Temperature reaches :temp degrees, the fan will turn on": "温度が :temp度 に達するとファンが作動します",
|
||||||
"Temperature reported: :temp°C": "温度が報告されました: :temp°C",
|
"Temperature reported: :temp°C": "温度が報告されました: :temp°C",
|
||||||
"Temperature updated to :temp°C": "温度が :temp°C に更新されました",
|
"Temperature updated to :temp°C": "温度が :temp°C に更新されました",
|
||||||
"Tenant": "顧客 (テナント)",
|
"Tenant": "顧客 (テナント)",
|
||||||
@ -2084,6 +2100,7 @@
|
|||||||
"Update Product": "商品更新",
|
"Update Product": "商品更新",
|
||||||
"Update Settings": "設定更新",
|
"Update Settings": "設定更新",
|
||||||
"Update Welcome Gift": "ウェルカムギフトを更新",
|
"Update Welcome Gift": "ウェルカムギフトを更新",
|
||||||
|
"Update ambient temperature setting to :temp°C": "環境温度設定を :temp°C に更新しました",
|
||||||
"Update existing role and permissions.": "既存のロールと権限設定を更新します。",
|
"Update existing role and permissions.": "既存のロールと権限設定を更新します。",
|
||||||
"Update failed": "更新失敗",
|
"Update failed": "更新失敗",
|
||||||
"Update identification for your asset": "資産の識別情報を更新",
|
"Update identification for your asset": "資産の識別情報を更新",
|
||||||
@ -2098,6 +2115,7 @@
|
|||||||
"Upload file or provide URL": "Upload file or provide URL",
|
"Upload file or provide URL": "Upload file or provide URL",
|
||||||
"Uploading new images will replace all existing images.": "新しい写真をアップロードすると、既存のすべての写真が置き換えられます。",
|
"Uploading new images will replace all existing images.": "新しい写真をアップロードすると、既存のすべての写真が置き換えられます。",
|
||||||
"Uploading...": "アップロード中...",
|
"Uploading...": "アップロード中...",
|
||||||
|
"Upper limit; the fan will turn on when reached": "温度上限;この温度に達するとファンが作動します",
|
||||||
"Usage Completed": "使用完了",
|
"Usage Completed": "使用完了",
|
||||||
"Usage Limit": "使用回数上限",
|
"Usage Limit": "使用回数上限",
|
||||||
"Usage Logs": "使用履歴",
|
"Usage Logs": "使用履歴",
|
||||||
@ -2343,9 +2361,7 @@
|
|||||||
"menu.sales.records": "販売記録",
|
"menu.sales.records": "販売記録",
|
||||||
"menu.sales.store-gifts": "来店ギフト",
|
"menu.sales.store-gifts": "来店ギフト",
|
||||||
"menu.special-permission": "特別権限",
|
"menu.special-permission": "特別権限",
|
||||||
"menu.special-permission.apk-versions": "APKバージョン",
|
|
||||||
"menu.special-permission.clear-stock": "在庫クリア",
|
"menu.special-permission.clear-stock": "在庫クリア",
|
||||||
"menu.special-permission.discord-notifications": "Discord通知",
|
|
||||||
"menu.warehouses": "倉庫管理",
|
"menu.warehouses": "倉庫管理",
|
||||||
"menu.warehouses.inventory": "在庫管理",
|
"menu.warehouses.inventory": "在庫管理",
|
||||||
"menu.warehouses.machine-inventory": "機器在庫概要",
|
"menu.warehouses.machine-inventory": "機器在庫概要",
|
||||||
|
|||||||
@ -120,6 +120,13 @@
|
|||||||
"All Warehouses": "所有倉庫",
|
"All Warehouses": "所有倉庫",
|
||||||
"All issues marked as resolved.": "所有異常已標記為已排除。",
|
"All issues marked as resolved.": "所有異常已標記為已排除。",
|
||||||
"All slots are fully stocked": "所有貨道已滿貨",
|
"All slots are fully stocked": "所有貨道已滿貨",
|
||||||
|
"Ambient Temperature": "環境溫度",
|
||||||
|
"Ambient Temperature Log": "環境溫度回傳",
|
||||||
|
"Ambient Temperature Monitoring": "環境溫度監測",
|
||||||
|
"Ambient Temperature Setting": "環境溫度設定",
|
||||||
|
"Ambient Temperature Trend": "環境溫度趨勢",
|
||||||
|
"Ambient Temperature Upper Limit": "環境溫度上限",
|
||||||
|
"Ambient temperature reported: :temp°C": "環境溫度回傳::temp°C",
|
||||||
"Amount": "金額",
|
"Amount": "金額",
|
||||||
"Amount / Payment": "金額 / 支付",
|
"Amount / Payment": "金額 / 支付",
|
||||||
"Amount Discount": "固定金額",
|
"Amount Discount": "固定金額",
|
||||||
@ -236,6 +243,7 @@
|
|||||||
"CASH": "CASH",
|
"CASH": "CASH",
|
||||||
"CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "建立倉庫間調撥或機台退庫單",
|
"CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": "建立倉庫間調撥或機台退庫單",
|
||||||
"Calculate Replenishment": "計算補貨量",
|
"Calculate Replenishment": "計算補貨量",
|
||||||
|
"Can be empty. If left blank, it means ambient temperature monitoring is disabled.": "可留空,留空表示不啟用監控功能",
|
||||||
"Cancel": "取消",
|
"Cancel": "取消",
|
||||||
"Cancel Gift": "取消此來店禮",
|
"Cancel Gift": "取消此來店禮",
|
||||||
"Cancel Order": "取消訂單",
|
"Cancel Order": "取消訂單",
|
||||||
@ -511,6 +519,7 @@
|
|||||||
"Disable Product Confirmation": "停用商品確認",
|
"Disable Product Confirmation": "停用商品確認",
|
||||||
"Disable Warehouse": "停用倉庫",
|
"Disable Warehouse": "停用倉庫",
|
||||||
"Disabled": "已停用",
|
"Disabled": "已停用",
|
||||||
|
"Disabled ambient temperature monitoring": "已停用環境溫度監測",
|
||||||
"Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "停用此客戶將會連帶停用該客戶下的所有帳號,確定要繼續嗎?",
|
"Disabling this customer will also disable all accounts under this customer. Are you sure you want to continue?": "停用此客戶將會連帶停用該客戶下的所有帳號,確定要繼續嗎?",
|
||||||
"Discord Notifications": "Discord通知",
|
"Discord Notifications": "Discord通知",
|
||||||
"Discord Webhook URL": "Discord Webhook 網址",
|
"Discord Webhook URL": "Discord Webhook 網址",
|
||||||
@ -632,6 +641,7 @@
|
|||||||
"English": "英文",
|
"English": "英文",
|
||||||
"Ensure your account is using a long, random password to stay secure.": "確保您的帳號使用了足夠強度的隨機密碼以維持安全。",
|
"Ensure your account is using a long, random password to stay secure.": "確保您的帳號使用了足夠強度的隨機密碼以維持安全。",
|
||||||
"Enter ad material name": "輸入廣告素材名稱",
|
"Enter ad material name": "輸入廣告素材名稱",
|
||||||
|
"Enter ambient temperature setting...": "請輸入環境溫度設定...",
|
||||||
"Enter full address": "輸入完整地址",
|
"Enter full address": "輸入完整地址",
|
||||||
"Enter login ID": "請輸入登入帳號",
|
"Enter login ID": "請輸入登入帳號",
|
||||||
"Enter machine location": "請輸入機台地點",
|
"Enter machine location": "請輸入機台地點",
|
||||||
@ -690,6 +700,10 @@
|
|||||||
"Failed to send test notification.": "發送測試通知失敗。",
|
"Failed to send test notification.": "發送測試通知失敗。",
|
||||||
"Failed to send test notification. Please verify the Webhook URL.": "發送測試通知失敗,請確認 Webhook 網址。",
|
"Failed to send test notification. Please verify the Webhook URL.": "發送測試通知失敗,請確認 Webhook 網址。",
|
||||||
"Failed to update machine images: ": "更新機台圖片失敗:",
|
"Failed to update machine images: ": "更新機台圖片失敗:",
|
||||||
|
"Fan Auto": "風扇自動",
|
||||||
|
"Fan Controls": "風扇控制",
|
||||||
|
"Fan Off": "關閉風扇",
|
||||||
|
"Fan On": "開啟風扇",
|
||||||
"Feature Settings": "功能設定",
|
"Feature Settings": "功能設定",
|
||||||
"Feature Toggles": "功能開關",
|
"Feature Toggles": "功能開關",
|
||||||
"Field": "欄位",
|
"Field": "欄位",
|
||||||
@ -822,6 +836,7 @@
|
|||||||
"Half Points Amount": "半點數金額",
|
"Half Points Amount": "半點數金額",
|
||||||
"Hardware & Network": "硬體與網路",
|
"Hardware & Network": "硬體與網路",
|
||||||
"Hardware & Slots": "硬體與貨道",
|
"Hardware & Slots": "硬體與貨道",
|
||||||
|
"Hardware Peripheral Settings": "硬體周邊設定",
|
||||||
"HashIV": "HashIV",
|
"HashIV": "HashIV",
|
||||||
"HashKey": "HashKey",
|
"HashKey": "HashKey",
|
||||||
"Heartbeat": "心跳狀態",
|
"Heartbeat": "心跳狀態",
|
||||||
@ -1955,6 +1970,7 @@
|
|||||||
"Temperature Limit": "溫度限制",
|
"Temperature Limit": "溫度限制",
|
||||||
"Temperature Restored": "溫度恢復正常",
|
"Temperature Restored": "溫度恢復正常",
|
||||||
"Temperature Trend": "溫度趨勢",
|
"Temperature Trend": "溫度趨勢",
|
||||||
|
"Temperature reaches :temp degrees, the fan will turn on": "溫度達 :temp度 將開啟風扇",
|
||||||
"Temperature reported: :temp°C": "回報溫度 : :temp°C",
|
"Temperature reported: :temp°C": "回報溫度 : :temp°C",
|
||||||
"Temperature updated to :temp°C": "溫度已更新為::temp°C",
|
"Temperature updated to :temp°C": "溫度已更新為::temp°C",
|
||||||
"Tenant": "客戶",
|
"Tenant": "客戶",
|
||||||
@ -2084,6 +2100,7 @@
|
|||||||
"Update Product": "更新商品",
|
"Update Product": "更新商品",
|
||||||
"Update Settings": "更新設定",
|
"Update Settings": "更新設定",
|
||||||
"Update Welcome Gift": "更新來店禮",
|
"Update Welcome Gift": "更新來店禮",
|
||||||
|
"Update ambient temperature setting to :temp°C": "更新環境溫度設定為 :temp°C",
|
||||||
"Update existing role and permissions.": "更新現有角色與權限設定。",
|
"Update existing role and permissions.": "更新現有角色與權限設定。",
|
||||||
"Update failed": "更新失敗",
|
"Update failed": "更新失敗",
|
||||||
"Update identification for your asset": "更新您的資產識別名稱",
|
"Update identification for your asset": "更新您的資產識別名稱",
|
||||||
@ -2098,6 +2115,7 @@
|
|||||||
"Upload file or provide URL": "上傳檔案或提供連結",
|
"Upload file or provide URL": "上傳檔案或提供連結",
|
||||||
"Uploading new images will replace all existing images.": "上傳新照片將會取代所有現有照片。",
|
"Uploading new images will replace all existing images.": "上傳新照片將會取代所有現有照片。",
|
||||||
"Uploading...": "上傳中...",
|
"Uploading...": "上傳中...",
|
||||||
|
"Upper limit; the fan will turn on when reached": "溫度上限,達到此溫度時風扇將會開啟",
|
||||||
"Usage Completed": "使用完成",
|
"Usage Completed": "使用完成",
|
||||||
"Usage Limit": "使用次數上限",
|
"Usage Limit": "使用次數上限",
|
||||||
"Usage Logs": "使用紀錄",
|
"Usage Logs": "使用紀錄",
|
||||||
@ -2343,9 +2361,7 @@
|
|||||||
"menu.sales.records": "銷售紀錄",
|
"menu.sales.records": "銷售紀錄",
|
||||||
"menu.sales.store-gifts": "來店禮",
|
"menu.sales.store-gifts": "來店禮",
|
||||||
"menu.special-permission": "特殊權限",
|
"menu.special-permission": "特殊權限",
|
||||||
"menu.special-permission.apk-versions": "APK版本",
|
|
||||||
"menu.special-permission.clear-stock": "庫存清空",
|
"menu.special-permission.clear-stock": "庫存清空",
|
||||||
"menu.special-permission.discord-notifications": "Discord通知",
|
|
||||||
"menu.warehouses": "倉儲管理",
|
"menu.warehouses": "倉儲管理",
|
||||||
"menu.warehouses.inventory": "庫存管理",
|
"menu.warehouses.inventory": "庫存管理",
|
||||||
"menu.warehouses.machine-inventory": "機台庫存概覽",
|
"menu.warehouses.machine-inventory": "機台庫存概覽",
|
||||||
|
|||||||
@ -106,7 +106,8 @@
|
|||||||
shopping_cart_enabled: machine.shopping_cart_enabled === true || machine.shopping_cart_enabled === 1 || machine.shopping_cart_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',
|
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',
|
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'
|
member_system_enabled: machine.member_system_enabled === true || machine.member_system_enabled === 1 || machine.member_system_enabled === '1',
|
||||||
|
ambient_temp_monitoring_enabled: machine.ambient_temp_monitoring_enabled === true || machine.ambient_temp_monitoring_enabled === 1 || machine.ambient_temp_monitoring_enabled === '1'
|
||||||
};
|
};
|
||||||
this.showMachineSettingsModal = true;
|
this.showMachineSettingsModal = true;
|
||||||
},
|
},
|
||||||
@ -610,6 +611,29 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</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">{{ __('Hardware Peripheral Settings') }}</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[ambient_temp_monitoring_enabled]" value="0">
|
||||||
|
<input type="checkbox" name="settings[ambient_temp_monitoring_enabled]" value="1"
|
||||||
|
x-model="machineSettings.ambient_temp_monitoring_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">{{
|
||||||
|
__('Ambient Temperature Monitoring') }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -82,6 +82,7 @@
|
|||||||
if ($machine->welcome_gift_enabled) $activeFeatures[] = __('Welcome Gift');
|
if ($machine->welcome_gift_enabled) $activeFeatures[] = __('Welcome Gift');
|
||||||
if ($machine->cash_module_enabled) $activeFeatures[] = __('Coin/Banknote Machine');
|
if ($machine->cash_module_enabled) $activeFeatures[] = __('Coin/Banknote Machine');
|
||||||
if ($machine->member_system_enabled) $activeFeatures[] = __('Member System');
|
if ($machine->member_system_enabled) $activeFeatures[] = __('Member System');
|
||||||
|
if ($machine->ambient_temp_monitoring_enabled) $activeFeatures[] = __('Ambient Temperature Monitoring');
|
||||||
@endphp
|
@endphp
|
||||||
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200">
|
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200">
|
||||||
<td class="px-6 py-6 w-1/4">
|
<td class="px-6 py-6 w-1/4">
|
||||||
@ -139,6 +140,7 @@
|
|||||||
if ($machine->welcome_gift_enabled) $activeFeatures[] = __('Welcome Gift');
|
if ($machine->welcome_gift_enabled) $activeFeatures[] = __('Welcome Gift');
|
||||||
if ($machine->cash_module_enabled) $activeFeatures[] = __('Coin/Banknote Machine');
|
if ($machine->cash_module_enabled) $activeFeatures[] = __('Coin/Banknote Machine');
|
||||||
if ($machine->member_system_enabled) $activeFeatures[] = __('Member System');
|
if ($machine->member_system_enabled) $activeFeatures[] = __('Member System');
|
||||||
|
if ($machine->ambient_temp_monitoring_enabled) $activeFeatures[] = __('Ambient Temperature Monitoring');
|
||||||
@endphp
|
@endphp
|
||||||
<div class="luxury-card p-5 rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group">
|
<div class="luxury-card p-5 rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group">
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,8 @@
|
|||||||
showResolveConfirm: false,
|
showResolveConfirm: false,
|
||||||
editMachineId: '',
|
editMachineId: '',
|
||||||
editMachineName: '',
|
editMachineName: '',
|
||||||
|
editAmbientTempSetting: '',
|
||||||
|
editAmbientTempMonitoringEnabled: false,
|
||||||
activeTab: 'status',
|
activeTab: 'status',
|
||||||
currentMachineId: '',
|
currentMachineId: '',
|
||||||
isLoadingTable: false,
|
isLoadingTable: false,
|
||||||
@ -40,6 +42,7 @@
|
|||||||
},
|
},
|
||||||
currentMachineSn: '',
|
currentMachineSn: '',
|
||||||
currentMachineName: '',
|
currentMachineName: '',
|
||||||
|
currentMachineAmbientTempEnabled: false,
|
||||||
logs: [],
|
logs: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
inventoryLoading: false,
|
inventoryLoading: false,
|
||||||
@ -55,6 +58,7 @@
|
|||||||
totalLogs: 0,
|
totalLogs: 0,
|
||||||
|
|
||||||
temperatureChart: null,
|
temperatureChart: null,
|
||||||
|
ambientTemperatureChart: null,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@ -63,17 +67,31 @@
|
|||||||
|
|
||||||
this.startDate = formatDate(now, '00:00');
|
this.startDate = formatDate(now, '00:00');
|
||||||
this.endDate = formatDate(now, '23:59');
|
this.endDate = formatDate(now, '23:59');
|
||||||
this.$watch('activeTab', () => this.fetchLogs(1));
|
this.$watch('activeTab', (val) => {
|
||||||
|
this.fetchLogs(1);
|
||||||
|
if (val === 'status') {
|
||||||
|
this.fetchTemperatureData();
|
||||||
|
} else if (val === 'ambient_temp') {
|
||||||
|
this.fetchAmbientTemperatureData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 監聽日期變動,同步更新圖表 (僅在當前為狀態分頁時)
|
// 監聽日期變動,同步更新圖表 (僅在當前為狀態分頁時)
|
||||||
this.$watch('startDate', () => { if(this.activeTab === 'status') this.fetchTemperatureData(); });
|
this.$watch('startDate', () => {
|
||||||
this.$watch('endDate', () => { if(this.activeTab === 'status') this.fetchTemperatureData(); });
|
if(this.activeTab === 'status') this.fetchTemperatureData();
|
||||||
|
if(this.activeTab === 'ambient_temp') this.fetchAmbientTemperatureData();
|
||||||
|
});
|
||||||
|
this.$watch('endDate', () => {
|
||||||
|
if(this.activeTab === 'status') this.fetchTemperatureData();
|
||||||
|
if(this.activeTab === 'ambient_temp') this.fetchAmbientTemperatureData();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async openLogPanel(id, sn, name) {
|
async openLogPanel(id, sn, name, ambientTempEnabled = false) {
|
||||||
this.currentMachineId = id;
|
this.currentMachineId = id;
|
||||||
this.currentMachineSn = sn;
|
this.currentMachineSn = sn;
|
||||||
this.currentMachineName = name;
|
this.currentMachineName = name;
|
||||||
|
this.currentMachineAmbientTempEnabled = ambientTempEnabled === true || ambientTempEnabled === 1 || ambientTempEnabled === '1' || ambientTempEnabled === 'true';
|
||||||
this.slots = [];
|
this.slots = [];
|
||||||
this.showLogPanel = true;
|
this.showLogPanel = true;
|
||||||
this.activeTab = 'status';
|
this.activeTab = 'status';
|
||||||
@ -96,6 +114,106 @@
|
|||||||
} catch (e) { console.error('fetchTemperatureData error:', e); }
|
} catch (e) { console.error('fetchTemperatureData error:', e); }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async fetchAmbientTemperatureData() {
|
||||||
|
if (this.activeTab !== 'ambient_temp') return;
|
||||||
|
try {
|
||||||
|
let url = `/admin/machines/${this.currentMachineId}/ambient-temperature-ajax?`;
|
||||||
|
if (this.startDate) url += '&start_date=' + this.startDate;
|
||||||
|
if (this.endDate) url += '&end_date=' + this.endDate;
|
||||||
|
|
||||||
|
const res = await fetch(url);
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
this.initAmbientTemperatureChart(data.data);
|
||||||
|
}
|
||||||
|
} catch (e) { console.error('fetchAmbientTemperatureData error:', e); }
|
||||||
|
},
|
||||||
|
|
||||||
|
initAmbientTemperatureChart(chartData) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const chartEl = document.querySelector("#ambient-temperature-chart");
|
||||||
|
if (!chartEl) return;
|
||||||
|
|
||||||
|
const isDark = document.documentElement.classList.contains('dark');
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
series: [{
|
||||||
|
name: "{{ __('Ambient Temperature') }}",
|
||||||
|
data: chartData
|
||||||
|
}],
|
||||||
|
chart: {
|
||||||
|
id: 'ambient-temp-chart',
|
||||||
|
type: 'area',
|
||||||
|
height: 200,
|
||||||
|
toolbar: { show: false },
|
||||||
|
zoom: { enabled: false },
|
||||||
|
animations: { enabled: false },
|
||||||
|
background: 'transparent',
|
||||||
|
accessibility: { enabled: false }
|
||||||
|
},
|
||||||
|
colors: ['#3b82f6'],
|
||||||
|
fill: {
|
||||||
|
type: 'gradient',
|
||||||
|
gradient: {
|
||||||
|
shadeIntensity: 1,
|
||||||
|
opacityFrom: 0.45,
|
||||||
|
opacityTo: 0.05,
|
||||||
|
stops: [20, 100]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataLabels: { enabled: false },
|
||||||
|
stroke: {
|
||||||
|
curve: 'smooth',
|
||||||
|
width: 3
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor: isDark ? '#1e293b' : '#f1f5f9',
|
||||||
|
strokeDashArray: 4,
|
||||||
|
padding: { left: 10, right: 10 }
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
style: { colors: '#94a3b8', fontSize: '10px', fontWeight: 600 },
|
||||||
|
datetimeUTC: false,
|
||||||
|
format: 'HH:mm',
|
||||||
|
hideOverlappingLabels: true,
|
||||||
|
},
|
||||||
|
axisBorder: { show: false },
|
||||||
|
axisTicks: { show: false },
|
||||||
|
tooltip: { enabled: false },
|
||||||
|
tickAmount: 6
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: { colors: '#94a3b8', fontSize: '10px', fontWeight: 600 },
|
||||||
|
formatter: (val) => val + '°C'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: isDark ? 'dark' : 'light',
|
||||||
|
x: {
|
||||||
|
show: true,
|
||||||
|
format: 'yyyy/MM/dd HH:mm:ss'
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
title: {
|
||||||
|
formatter: (seriesName) => seriesName + ': '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.ambientTemperatureChart) {
|
||||||
|
this.ambientTemperatureChart.updateOptions(options);
|
||||||
|
} else {
|
||||||
|
this.ambientTemperatureChart = new ApexCharts(chartEl, options);
|
||||||
|
this.ambientTemperatureChart.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
initTemperatureChart(chartData) {
|
initTemperatureChart(chartData) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const chartEl = document.querySelector("#temperature-chart");
|
const chartEl = document.querySelector("#temperature-chart");
|
||||||
@ -181,9 +299,11 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openEditModal(id, name) {
|
openEditModal(id, name, ambientTempSetting = '', ambientTempMonitoringEnabled = false) {
|
||||||
this.editMachineId = id;
|
this.editMachineId = id;
|
||||||
this.editMachineName = name;
|
this.editMachineName = name;
|
||||||
|
this.editAmbientTempSetting = ambientTempSetting;
|
||||||
|
this.editAmbientTempMonitoringEnabled = ambientTempMonitoringEnabled === true || ambientTempMonitoringEnabled === 1 || ambientTempMonitoringEnabled === '1' || ambientTempMonitoringEnabled === 'true';
|
||||||
this.showEditModal = true;
|
this.showEditModal = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -356,7 +476,7 @@
|
|||||||
<tr
|
<tr
|
||||||
class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200">
|
class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200">
|
||||||
<td class="px-6 py-6 cursor-pointer group"
|
<td class="px-6 py-6 cursor-pointer group"
|
||||||
@click="openLogPanel('{{ $machine->id }}', '{{ $machine->serial_no }}', '{{ addslashes($machine->name) }}')">
|
@click="openLogPanel('{{ $machine->id }}', '{{ $machine->serial_no }}', '{{ addslashes($machine->name) }}', {{ $machine->ambient_temp_monitoring_enabled ? 1 : 0 }})">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div
|
<div
|
||||||
class="w-12 h-12 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden shadow-sm">
|
class="w-12 h-12 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden shadow-sm">
|
||||||
@ -493,7 +613,7 @@
|
|||||||
<div class="flex items-center justify-end gap-2">
|
<div class="flex items-center justify-end gap-2">
|
||||||
|
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@click="openEditModal('{{ $machine->id }}', '{{ addslashes($machine->name) }}')"
|
@click="openEditModal('{{ $machine->id }}', '{{ addslashes($machine->name) }}', '{{ $machine->ambient_temp_setting }}', '{{ $machine->ambient_temp_monitoring_enabled ? 1 : 0 }}')"
|
||||||
class="p-2.5 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all duration-200"
|
class="p-2.5 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all duration-200"
|
||||||
title="{{ __('Edit Name') }}">
|
title="{{ __('Edit Name') }}">
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor"
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor"
|
||||||
@ -503,7 +623,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@click="openLogPanel('{{ $machine->id }}', '{{ $machine->serial_no }}', '{{ addslashes($machine->name) }}')"
|
@click="openLogPanel('{{ $machine->id }}', '{{ $machine->serial_no }}', '{{ addslashes($machine->name) }}', {{ $machine->ambient_temp_monitoring_enabled ? 1 : 0 }})"
|
||||||
class="p-2.5 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all duration-200"
|
class="p-2.5 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all duration-200"
|
||||||
title="{{ __('View Logs') }}">
|
title="{{ __('View Logs') }}">
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor"
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor"
|
||||||
@ -543,7 +663,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<h3 @click="openLogPanel('{{ $machine->id }}', '{{ $machine->serial_no }}', '{{ addslashes($machine->name) }}')"
|
<h3 @click="openLogPanel('{{ $machine->id }}', '{{ $machine->serial_no }}', '{{ addslashes($machine->name) }}', {{ $machine->ambient_temp_monitoring_enabled ? 1 : 0 }})"
|
||||||
class="text-base font-extrabold text-slate-800 dark:text-slate-100 hover:text-cyan-600 dark:hover:text-cyan-400 transition-colors tracking-tight cursor-pointer flex items-center gap-2 min-w-0">
|
class="text-base font-extrabold text-slate-800 dark:text-slate-100 hover:text-cyan-600 dark:hover:text-cyan-400 transition-colors tracking-tight cursor-pointer flex items-center gap-2 min-w-0">
|
||||||
<span class="truncate">{{ $machine->name }}</span>
|
<span class="truncate">{{ $machine->name }}</span>
|
||||||
@if($machine->has_login_warning)
|
@if($machine->has_login_warning)
|
||||||
@ -622,7 +742,7 @@
|
|||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@click="openEditModal('{{ $machine->id }}', '{{ addslashes($machine->name) }}')"
|
@click="openEditModal('{{ $machine->id }}', '{{ addslashes($machine->name) }}', '{{ $machine->ambient_temp_setting }}', '{{ $machine->ambient_temp_monitoring_enabled ? 1 : 0 }}')"
|
||||||
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-500 dark:text-slate-400 font-bold text-xs hover:bg-cyan-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50">
|
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-500 dark:text-slate-400 font-bold text-xs hover:bg-cyan-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50">
|
||||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5"
|
||||||
@ -631,8 +751,8 @@
|
|||||||
{{ __('Edit') }}
|
{{ __('Edit') }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@click="openLogPanel('{{ $machine->id }}', '{{ $machine->serial_no }}', '{{ addslashes($machine->name) }}')"
|
@click="openLogPanel('{{ $machine->id }}', '{{ $machine->serial_no }}', '{{ addslashes($machine->name) }}', {{ $machine->ambient_temp_monitoring_enabled ? 1 : 0 }})"
|
||||||
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-500 dark:text-slate-400 font-bold text-xs hover:bg-cyan-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50">
|
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-500 dark:text-slate-400 font-bold text-xs hover:bg-cyan-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50">
|
||||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5"
|
||||||
d="M2.036 12.322a1.012 1.012 0 0 1 0-.644C3.67 8.5 7.652 6 12 6c4.348 0 8.332 2.5 9.964 5.678a1.012 1.012 0 0 1 0 .644C20.33 15.5 16.348 18 12 18c-4.348 0-8.332-2.5-9.964-5.678z" />
|
d="M2.036 12.322a1.012 1.012 0 0 1 0-.644C3.67 8.5 7.652 6 12 6c4.348 0 8.332 2.5 9.964 5.678a1.012 1.012 0 0 1 0 .644C20.33 15.5 16.348 18 12 18c-4.348 0-8.332-2.5-9.964-5.678z" />
|
||||||
@ -802,18 +922,25 @@
|
|||||||
class="whitespace-nowrap py-4 px-1 border-b-2 font-bold text-[13px] sm:text-sm transition duration-300">
|
class="whitespace-nowrap py-4 px-1 border-b-2 font-bold text-[13px] sm:text-sm transition duration-300">
|
||||||
{{ __('Machine Login Logs') }}
|
{{ __('Machine Login Logs') }}
|
||||||
</button>
|
</button>
|
||||||
<button @click="activeTab = 'submachine'"
|
<button @click="activeTab = 'submachine'"
|
||||||
:class="{'border-cyan-500 text-cyan-600 dark:text-cyan-400': activeTab === 'submachine', 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300 dark:hover:text-slate-300': activeTab !== 'submachine'}"
|
:class="{'border-cyan-500 text-cyan-600 dark:text-cyan-400': activeTab === 'submachine', 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300 dark:hover:text-slate-300': activeTab !== 'submachine'}"
|
||||||
class="whitespace-nowrap py-4 px-1 border-b-2 font-bold text-[13px] sm:text-sm transition duration-300">
|
class="whitespace-nowrap py-4 px-1 border-b-2 font-bold text-[13px] sm:text-sm transition duration-300">
|
||||||
{{ __('Sub-machine Status Request') }}
|
{{ __('Sub-machine Status Request') }}
|
||||||
</button>
|
</button>
|
||||||
|
<button @click="activeTab = 'ambient_temp'"
|
||||||
|
x-show="currentMachineAmbientTempEnabled"
|
||||||
|
:class="{'border-cyan-500 text-cyan-600 dark:text-cyan-400': activeTab === 'ambient_temp', 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300 dark:hover:text-slate-300': activeTab !== 'ambient_temp'}"
|
||||||
|
class="whitespace-nowrap py-4 px-1 border-b-2 font-bold text-[13px] sm:text-sm transition duration-300"
|
||||||
|
x-cloak>
|
||||||
|
{{ __('Ambient Temperature Log') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab Contents -->
|
<!-- Tab Contents -->
|
||||||
<div class="flex-1 overflow-y-auto p-6 sm:p-8">
|
<div class="flex-1 overflow-y-auto p-6 sm:p-8">
|
||||||
|
|
||||||
<div class="relative min-h-[400px]">
|
<div class="relative min-h-[400px]">
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div x-show="loading" x-transition:enter="transition ease-out duration-300"
|
<div x-show="loading" x-transition:enter="transition ease-out duration-300"
|
||||||
@ -822,7 +949,7 @@
|
|||||||
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
|
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
|
||||||
class="absolute inset-0 z-50 flex flex-col items-center justify-center bg-white/40 dark:bg-slate-900/40 backdrop-blur-[1px] rounded-2xl"
|
class="absolute inset-0 z-50 flex flex-col items-center justify-center bg-white/40 dark:bg-slate-900/40 backdrop-blur-[1px] rounded-2xl"
|
||||||
x-cloak>
|
x-cloak>
|
||||||
|
|
||||||
<div class="relative w-16 h-16 mb-4 flex items-center justify-center">
|
<div class="relative w-16 h-16 mb-4 flex items-center justify-center">
|
||||||
<!-- 外圈:快速旋轉 -->
|
<!-- 外圈:快速旋轉 -->
|
||||||
<div
|
<div
|
||||||
@ -844,7 +971,7 @@
|
|||||||
class="text-[12px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">
|
class="text-[12px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">
|
||||||
{{ __('Loading Data') }}...</p>
|
{{ __('Loading Data') }}...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Temperature Trend Chart -->
|
<!-- Temperature Trend Chart -->
|
||||||
<div x-show="activeTab === 'status'"
|
<div x-show="activeTab === 'status'"
|
||||||
class="mb-6 p-4 rounded-2xl bg-slate-50/50 dark:bg-white/[0.02] border border-slate-100 dark:border-slate-800/50">
|
class="mb-6 p-4 rounded-2xl bg-slate-50/50 dark:bg-white/[0.02] border border-slate-100 dark:border-slate-800/50">
|
||||||
@ -859,6 +986,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="temperature-chart" class="min-h-[200px] w-full"></div>
|
<div id="temperature-chart" class="min-h-[200px] w-full"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Ambient Temperature Trend Chart -->
|
||||||
|
<div x-show="activeTab === 'ambient_temp'"
|
||||||
|
class="mb-6 p-4 rounded-2xl bg-slate-50/50 dark:bg-white/[0.02] border border-slate-100 dark:border-slate-800/50" x-cloak>
|
||||||
|
<div class="flex items-center justify-between mb-4 px-2">
|
||||||
|
<h3 class="text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] flex items-center gap-2">
|
||||||
|
<span class="w-1.5 h-1.5 rounded-full bg-blue-500 shadow-[0_0_8px_rgba(59,130,246,0.5)]"></span>
|
||||||
|
{{ __('Ambient Temperature Trend') }}
|
||||||
|
</h3>
|
||||||
|
<span class="text-[10px] font-bold text-slate-400" x-show="logs.length > 0">
|
||||||
|
<span x-text="currentMachineName"></span> @ <span x-text="startDate.split(' ')[0]"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div id="ambient-temperature-chart" class="min-h-[200px] w-full"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Logs Container -->
|
<!-- Logs Container -->
|
||||||
<div x-show="activeTab !== 'expiry'"
|
<div x-show="activeTab !== 'expiry'"
|
||||||
@ -1063,6 +1205,56 @@
|
|||||||
class="luxury-input block w-full px-6 py-4 text-base font-bold text-slate-800 dark:text-white bg-slate-50/50 dark:bg-slate-900/50"
|
class="luxury-input block w-full px-6 py-4 text-base font-bold text-slate-800 dark:text-white bg-slate-50/50 dark:bg-slate-900/50"
|
||||||
placeholder="{{ __('Enter machine name...') }}">
|
placeholder="{{ __('Enter machine name...') }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4" x-show="editAmbientTempMonitoringEnabled" x-cloak>
|
||||||
|
<label
|
||||||
|
class="block text-xs font-black text-slate-500 dark:text-slate-400 uppercase tracking-[0.1em]">{{
|
||||||
|
__('Ambient Temperature Upper Limit') }} ({{ __('Upper limit; the fan will turn on when reached') }})</label>
|
||||||
|
|
||||||
|
{{-- Luxury +/- Counter UI --}}
|
||||||
|
<div class="flex items-center h-14 rounded-2xl border border-slate-200/50 dark:border-slate-700/50 bg-slate-50/50 dark:bg-slate-900/50 overflow-hidden w-full group focus-within:ring-2 focus-within:ring-cyan-500/20 transition-all">
|
||||||
|
{{-- Minus Button --}}
|
||||||
|
<button type="button" @click="editAmbientTempSetting = (editAmbientTempSetting ? Math.max(0, parseInt(editAmbientTempSetting) - 1) : 25)"
|
||||||
|
class="shrink-0 w-14 h-full flex items-center justify-center text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 active:scale-90 transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M20 12H4"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="h-6 w-px bg-slate-200 dark:bg-slate-700/50"></div>
|
||||||
|
|
||||||
|
{{-- Input Field --}}
|
||||||
|
<div class="flex-1 relative flex items-center">
|
||||||
|
<input type="number" name="ambient_temp_setting" x-model="editAmbientTempSetting"
|
||||||
|
class="w-full bg-transparent border-none text-center text-base font-extrabold text-slate-800 dark:text-white focus:ring-0 p-0 placeholder:text-slate-400 placeholder:font-bold placeholder:text-sm [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
||||||
|
placeholder="{{ __('Enter ambient temperature setting...') }}">
|
||||||
|
|
||||||
|
{{-- Clear Button --}}
|
||||||
|
<button type="button" x-show="editAmbientTempSetting !== '' && editAmbientTempSetting !== null" @click="editAmbientTempSetting = ''"
|
||||||
|
class="absolute right-3 p-1 rounded-full text-slate-400 hover:text-rose-500 hover:bg-rose-500/10 active:scale-95 transition-all"
|
||||||
|
style="display: none;">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-6 w-px bg-slate-200 dark:bg-slate-700/50"></div>
|
||||||
|
|
||||||
|
{{-- Plus Button --}}
|
||||||
|
<button type="button" @click="editAmbientTempSetting = (editAmbientTempSetting ? parseInt(editAmbientTempSetting) + 1 : 25)"
|
||||||
|
class="shrink-0 w-14 h-full flex items-center justify-center text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 active:scale-90 transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14M5 12h14"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Help Text --}}
|
||||||
|
<p class="text-[11px] font-bold text-slate-400 dark:text-slate-500 tracking-wide mt-1 pl-1">
|
||||||
|
{{ __('Can be empty. If left blank, it means ambient temperature monitoring is disabled.') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-4 pt-4">
|
<div class="flex items-center gap-4 pt-4">
|
||||||
<button type="button" @click="showEditModal = false"
|
<button type="button" @click="showEditModal = false"
|
||||||
|
|||||||
@ -38,6 +38,11 @@
|
|||||||
'Remote Settlement' => __('Remote Settlement'),
|
'Remote Settlement' => __('Remote Settlement'),
|
||||||
'Lock Page Lock' => __('Lock Page Lock'),
|
'Lock Page Lock' => __('Lock Page Lock'),
|
||||||
'Lock Page Unlock' => __('Lock Page Unlock'),
|
'Lock Page Unlock' => __('Lock Page Unlock'),
|
||||||
|
'Fan On' => __('Fan On'),
|
||||||
|
'Fan Off' => __('Fan Off'),
|
||||||
|
'Fan Auto' => __('Fan Auto'),
|
||||||
|
'Ambient Temperature' => __('Ambient Temperature'),
|
||||||
|
'Ambient Temperature Upper Limit' => __('Ambient Temperature Upper Limit'),
|
||||||
'Remote Change' => __('Remote Change'),
|
'Remote Change' => __('Remote Change'),
|
||||||
'Remote Dispense' => __('Remote Dispense'),
|
'Remote Dispense' => __('Remote Dispense'),
|
||||||
'Adjust Stock & Expiry' => __('Adjust Stock & Expiry'),
|
'Adjust Stock & Expiry' => __('Adjust Stock & Expiry'),
|
||||||
@ -442,6 +447,10 @@
|
|||||||
'checkout': this.translations['Remote Settlement'],
|
'checkout': this.translations['Remote Settlement'],
|
||||||
'lock': this.translations['Lock Page Lock'],
|
'lock': this.translations['Lock Page Lock'],
|
||||||
'unlock': this.translations['Lock Page Unlock'],
|
'unlock': this.translations['Lock Page Unlock'],
|
||||||
|
'fanon': this.translations['Fan On'],
|
||||||
|
'fanoff': this.translations['Fan Off'],
|
||||||
|
'fanauto': this.translations['Fan Auto'],
|
||||||
|
'ambient_temp_limit': this.translations['Ambient Temperature Upper Limit'],
|
||||||
'change': this.translations['Remote Change'],
|
'change': this.translations['Remote Change'],
|
||||||
'dispense': this.translations['Remote Dispense'],
|
'dispense': this.translations['Remote Dispense'],
|
||||||
'reload_stock': this.translations['Adjust Stock & Expiry'],
|
'reload_stock': this.translations['Adjust Stock & Expiry'],
|
||||||
@ -512,6 +521,11 @@
|
|||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.command_type === 'ambient_temp_limit' && item.payload) {
|
||||||
|
const temp = item.payload.temperature;
|
||||||
|
return `${this.translations['Ambient Temperature Upper Limit']}: ${temp !== null && temp !== undefined ? temp + '°C' : this.translations['Empty']}`;
|
||||||
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -685,6 +699,66 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Fan Controls -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-center gap-3 ml-1">
|
||||||
|
<div class="w-1 h-3 bg-slate-300 dark:bg-slate-700 rounded-full"></div>
|
||||||
|
<span
|
||||||
|
class="text-[10px] font-black text-slate-400 uppercase tracking-[0.2em]">{{
|
||||||
|
__('Fan Controls') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<!-- Fan On -->
|
||||||
|
<button @click="sendCommand('fanon')"
|
||||||
|
class="p-6 rounded-3xl border border-slate-100 dark:border-slate-800 flex items-center gap-5 hover:border-cyan-500/50 dark:hover:border-cyan-400/60 hover:bg-cyan-500/5 dark:hover:bg-cyan-400/5 group transition-all bg-white/50 dark:bg-slate-900/40 shadow-sm">
|
||||||
|
<div
|
||||||
|
class="w-12 h-12 rounded-2xl bg-cyan-500/10 flex items-center justify-center text-cyan-500 dark:text-cyan-400 group-hover:scale-110 transition-transform duration-500 border border-cyan-500/20 dark:border-cyan-400/20">
|
||||||
|
<svg class="w-6 h-6 animate-[spin_10s_linear_infinite]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-left">
|
||||||
|
<div
|
||||||
|
class="text-sm font-black text-slate-800 dark:text-white group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">
|
||||||
|
{{ __('Fan On') }}</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Fan Off -->
|
||||||
|
<button @click="sendCommand('fanoff')"
|
||||||
|
class="p-6 rounded-3xl border border-slate-100 dark:border-slate-800 flex items-center gap-5 hover:border-cyan-500/50 dark:hover:border-cyan-400/60 hover:bg-cyan-500/5 dark:hover:bg-cyan-400/5 group transition-all bg-white/50 dark:bg-slate-900/40 shadow-sm">
|
||||||
|
<div
|
||||||
|
class="w-12 h-12 rounded-2xl bg-cyan-500/10 flex items-center justify-center text-cyan-500 dark:text-cyan-400 group-hover:scale-110 transition-transform duration-500 border border-cyan-500/20 dark:border-cyan-400/20">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-left">
|
||||||
|
<div
|
||||||
|
class="text-sm font-black text-slate-800 dark:text-white group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">
|
||||||
|
{{ __('Fan Off') }}</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Fan Auto -->
|
||||||
|
<button @click="sendCommand('fanauto')"
|
||||||
|
class="p-6 rounded-3xl border border-slate-100 dark:border-slate-800 flex items-center gap-5 hover:border-cyan-500/50 dark:hover:border-cyan-400/60 hover:bg-cyan-500/5 dark:hover:bg-cyan-400/5 group transition-all bg-white/50 dark:bg-slate-900/40 shadow-sm">
|
||||||
|
<div
|
||||||
|
class="w-12 h-12 rounded-2xl bg-cyan-500/10 flex items-center justify-center text-cyan-500 dark:text-cyan-400 group-hover:scale-110 transition-transform duration-500 border border-cyan-500/20 dark:border-cyan-400/20">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-left">
|
||||||
|
<div
|
||||||
|
class="text-sm font-black text-slate-800 dark:text-white group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">
|
||||||
|
{{ __('Fan Auto') }}</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Security Controls -->
|
<!-- Security Controls -->
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex items-center gap-3 ml-1">
|
<div class="flex items-center gap-3 ml-1">
|
||||||
|
|||||||
@ -89,6 +89,10 @@
|
|||||||
'checkout' => __('Remote Settlement'),
|
'checkout' => __('Remote Settlement'),
|
||||||
'lock' => __('Lock Page Lock'),
|
'lock' => __('Lock Page Lock'),
|
||||||
'unlock' => __('Lock Page Unlock'),
|
'unlock' => __('Lock Page Unlock'),
|
||||||
|
'fanon' => __('Fan On'),
|
||||||
|
'fanoff' => __('Fan Off'),
|
||||||
|
'fanauto' => __('Fan Auto'),
|
||||||
|
'ambient_temp_limit' => __('Ambient Temperature Upper Limit'),
|
||||||
'change' => __('Remote Change'),
|
'change' => __('Remote Change'),
|
||||||
'dispense' => __('Remote Dispense'),
|
'dispense' => __('Remote Dispense'),
|
||||||
'update_ads' => __('Sync Ads'),
|
'update_ads' => __('Sync Ads'),
|
||||||
|
|||||||
@ -63,6 +63,7 @@ Route::middleware(['auth', 'auth.session', 'verified', 'tenant.access'])->prefix
|
|||||||
Route::post('/{machine}/slots/expiry', [App\Http\Controllers\Admin\MachineController::class, 'updateSlotExpiry'])->name('slots.expiry.update');
|
Route::post('/{machine}/slots/expiry', [App\Http\Controllers\Admin\MachineController::class, 'updateSlotExpiry'])->name('slots.expiry.update');
|
||||||
Route::get('/{machine}/logs-ajax', [App\Http\Controllers\Admin\MachineController::class, 'logsAjax'])->name('logs-ajax');
|
Route::get('/{machine}/logs-ajax', [App\Http\Controllers\Admin\MachineController::class, 'logsAjax'])->name('logs-ajax');
|
||||||
Route::get('/{machine}/temperature-ajax', [App\Http\Controllers\Admin\MachineController::class, 'temperatureAjax'])->name('temperature-ajax');
|
Route::get('/{machine}/temperature-ajax', [App\Http\Controllers\Admin\MachineController::class, 'temperatureAjax'])->name('temperature-ajax');
|
||||||
|
Route::get('/{machine}/ambient-temperature-ajax', [App\Http\Controllers\Admin\MachineController::class, 'ambientTemperatureAjax'])->name('ambient-temperature-ajax');
|
||||||
Route::post('/{machine}/resolve-logs', [App\Http\Controllers\Admin\MachineController::class, 'resolveLogs'])->name('resolve-logs');
|
Route::post('/{machine}/resolve-logs', [App\Http\Controllers\Admin\MachineController::class, 'resolveLogs'])->name('resolve-logs');
|
||||||
});
|
});
|
||||||
Route::resource('machines', App\Http\Controllers\Admin\MachineController::class);
|
Route::resource('machines', App\Http\Controllers\Admin\MachineController::class);
|
||||||
|
|||||||
@ -1,253 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
files = {
|
|
||||||
'zh_TW': '/home/mama/projects/star-cloud/lang/zh_TW.json',
|
|
||||||
'en': '/home/mama/projects/star-cloud/lang/en.json',
|
|
||||||
'ja': '/home/mama/projects/star-cloud/lang/ja.json'
|
|
||||||
}
|
|
||||||
|
|
||||||
new_keys = {
|
|
||||||
"CREATE STOCK TRANSFERS BETWEEN WAREHOUSES AND MACHINE RETURNS": {
|
|
||||||
"zh_TW": "建立倉庫間調撥或機台退庫單",
|
|
||||||
"en": "Create stock transfers between warehouses and machine returns",
|
|
||||||
"ja": "倉庫間の転送および機台からの返品伝票を作成"
|
|
||||||
},
|
|
||||||
"Create and manage replenishment orders from warehouse to machines": {
|
|
||||||
"zh_TW": "建立並管理從倉庫到機台的補貨單",
|
|
||||||
"en": "Create and manage replenishment orders from warehouse to machines",
|
|
||||||
"ja": "倉庫から機台への補充伝票の作成と管理"
|
|
||||||
},
|
|
||||||
"Create stock transfers between warehouses and machine returns": {
|
|
||||||
"zh_TW": "建立倉庫間調撥或機台退庫單",
|
|
||||||
"en": "Create stock transfers between warehouses and machine returns",
|
|
||||||
"ja": "倉庫間の転送および機台からの返品伝票を作成"
|
|
||||||
},
|
|
||||||
"Create Transfer Order": {
|
|
||||||
"zh_TW": "建立調撥單",
|
|
||||||
"en": "Create Transfer Order",
|
|
||||||
"ja": "転送伝票を作成"
|
|
||||||
},
|
|
||||||
"Create Replenishment Order": {
|
|
||||||
"zh_TW": "建立補貨單",
|
|
||||||
"en": "Create Replenishment Order",
|
|
||||||
"ja": "補充伝票を作成"
|
|
||||||
},
|
|
||||||
"New Transfer": {
|
|
||||||
"zh_TW": "新增調撥單",
|
|
||||||
"en": "New Transfer",
|
|
||||||
"ja": "新規転送"
|
|
||||||
},
|
|
||||||
"New Replenishment": {
|
|
||||||
"zh_TW": "新增補貨單",
|
|
||||||
"en": "New Replenishment",
|
|
||||||
"ja": "新規補充"
|
|
||||||
},
|
|
||||||
"New Stock-In Order": {
|
|
||||||
"zh_TW": "新增進貨單",
|
|
||||||
"en": "New Stock-In Order",
|
|
||||||
"ja": "新規入庫伝票"
|
|
||||||
},
|
|
||||||
"No transfer orders found": {
|
|
||||||
"zh_TW": "查無調撥單紀錄",
|
|
||||||
"en": "No transfer orders found",
|
|
||||||
"ja": "転送伝票が見つかりません"
|
|
||||||
},
|
|
||||||
"No replenishment orders found": {
|
|
||||||
"zh_TW": "查無補貨單紀錄",
|
|
||||||
"en": "No replenishment orders found",
|
|
||||||
"ja": "補充伝票が見つかりません"
|
|
||||||
},
|
|
||||||
"No stock data found": {
|
|
||||||
"zh_TW": "查無庫存資料",
|
|
||||||
"en": "No stock data found",
|
|
||||||
"ja": "在庫データが見つかりません"
|
|
||||||
},
|
|
||||||
"No stock-in orders found": {
|
|
||||||
"zh_TW": "查無進貨單紀錄",
|
|
||||||
"en": "No stock-in orders found",
|
|
||||||
"ja": "入庫伝票が見つかりません"
|
|
||||||
},
|
|
||||||
"No movement records found": {
|
|
||||||
"zh_TW": "查無異動紀錄",
|
|
||||||
"en": "No movement records found",
|
|
||||||
"ja": "移動履歴が見つかりません"
|
|
||||||
},
|
|
||||||
"Replenishment Orders": {
|
|
||||||
"zh_TW": "機台補貨單",
|
|
||||||
"en": "Replenishment Orders",
|
|
||||||
"ja": "機台補充伝票"
|
|
||||||
},
|
|
||||||
"Search products...": {
|
|
||||||
"zh_TW": "搜尋商品...",
|
|
||||||
"en": "Search products...",
|
|
||||||
"ja": "商品を検索..."
|
|
||||||
},
|
|
||||||
"Search Product": {
|
|
||||||
"zh_TW": "搜尋商品",
|
|
||||||
"en": "Search Product",
|
|
||||||
"ja": "商品を検索"
|
|
||||||
},
|
|
||||||
"Select Warehouse": {
|
|
||||||
"zh_TW": "選擇倉庫",
|
|
||||||
"en": "Select Warehouse",
|
|
||||||
"ja": "倉庫を選択"
|
|
||||||
},
|
|
||||||
"Select Machine": {
|
|
||||||
"zh_TW": "選擇機台",
|
|
||||||
"en": "Select Machine",
|
|
||||||
"ja": "機台を選択"
|
|
||||||
},
|
|
||||||
"Select Product": {
|
|
||||||
"zh_TW": "選擇商品",
|
|
||||||
"en": "Select Product",
|
|
||||||
"ja": "商品を選択"
|
|
||||||
},
|
|
||||||
"Source Warehouse": {
|
|
||||||
"zh_TW": "來源倉庫",
|
|
||||||
"en": "Source Warehouse",
|
|
||||||
"ja": "発送元倉庫"
|
|
||||||
},
|
|
||||||
"Target Warehouse": {
|
|
||||||
"zh_TW": "目標倉庫",
|
|
||||||
"en": "Target Warehouse",
|
|
||||||
"ja": "対象倉庫"
|
|
||||||
},
|
|
||||||
"Stock-In Orders": {
|
|
||||||
"zh_TW": "進貨單管理",
|
|
||||||
"en": "Stock-In Orders",
|
|
||||||
"ja": "入庫伝票"
|
|
||||||
},
|
|
||||||
"Stock-In Management": {
|
|
||||||
"zh_TW": "進貨管理",
|
|
||||||
"en": "Stock-In Management",
|
|
||||||
"ja": "入庫管理"
|
|
||||||
},
|
|
||||||
"Stock flow history": {
|
|
||||||
"zh_TW": "庫存流向歷史",
|
|
||||||
"en": "Stock flow history",
|
|
||||||
"ja": "在庫移動履歴"
|
|
||||||
},
|
|
||||||
"Stock-In Orders Tab": {
|
|
||||||
"zh_TW": "進貨單管理",
|
|
||||||
"en": "Stock-In Orders",
|
|
||||||
"ja": "入庫伝票"
|
|
||||||
},
|
|
||||||
"Transfer Orders": {
|
|
||||||
"zh_TW": "調撥單管理",
|
|
||||||
"en": "Transfer Orders",
|
|
||||||
"ja": "転送伝票"
|
|
||||||
},
|
|
||||||
"Track stock-in orders and movement history": {
|
|
||||||
"zh_TW": "追蹤進貨單據與庫存異動紀錄",
|
|
||||||
"en": "Track stock-in orders and movement history",
|
|
||||||
"ja": "入庫伝票および移動履歴の追跡"
|
|
||||||
},
|
|
||||||
"Warehouse to Warehouse": {
|
|
||||||
"zh_TW": "倉庫對倉庫",
|
|
||||||
"en": "Warehouse to Warehouse",
|
|
||||||
"ja": "倉庫間転送"
|
|
||||||
},
|
|
||||||
"Warehouse Transfer": {
|
|
||||||
"zh_TW": "倉庫調撥",
|
|
||||||
"en": "Warehouse Transfer",
|
|
||||||
"ja": "倉庫転送"
|
|
||||||
},
|
|
||||||
"Warehouse Management": {
|
|
||||||
"zh_TW": "倉儲管理",
|
|
||||||
"en": "Warehouse Management",
|
|
||||||
"ja": "倉庫管理"
|
|
||||||
},
|
|
||||||
"Warehouse purchase records": {
|
|
||||||
"zh_TW": "倉庫採購進貨紀錄",
|
|
||||||
"en": "Warehouse purchase records",
|
|
||||||
"ja": "倉庫購入記録"
|
|
||||||
},
|
|
||||||
"Machine to Warehouse": {
|
|
||||||
"zh_TW": "機台對倉庫",
|
|
||||||
"en": "Machine to Warehouse",
|
|
||||||
"ja": "機台から倉庫"
|
|
||||||
},
|
|
||||||
"Machine Return": {
|
|
||||||
"zh_TW": "機台退庫",
|
|
||||||
"en": "Machine Return",
|
|
||||||
"ja": "機台返品"
|
|
||||||
},
|
|
||||||
"Machine Replenishment": {
|
|
||||||
"zh_TW": "機台補貨單",
|
|
||||||
"en": "Machine Replenishment",
|
|
||||||
"ja": "機台補充"
|
|
||||||
},
|
|
||||||
"Movement History": {
|
|
||||||
"zh_TW": "異動紀錄",
|
|
||||||
"en": "Movement History",
|
|
||||||
"ja": "移動履歴"
|
|
||||||
},
|
|
||||||
"Movement Logs": {
|
|
||||||
"zh_TW": "異動紀錄",
|
|
||||||
"en": "Movement Logs",
|
|
||||||
"ja": "移動ログ"
|
|
||||||
},
|
|
||||||
"Manage stock transfers between warehouses and machine returns": {
|
|
||||||
"zh_TW": "建立倉庫間調撥或機台退庫單",
|
|
||||||
"en": "Manage stock transfers between warehouses and machine returns",
|
|
||||||
"ja": "倉庫間の転送および機台からの返品伝票を作成"
|
|
||||||
},
|
|
||||||
"Main": {
|
|
||||||
"zh_TW": "總倉",
|
|
||||||
"en": "Main",
|
|
||||||
"ja": "総倉庫"
|
|
||||||
},
|
|
||||||
"Note": {
|
|
||||||
"zh_TW": "備註",
|
|
||||||
"en": "Note",
|
|
||||||
"ja": "備考"
|
|
||||||
},
|
|
||||||
"Transfer Audit": {
|
|
||||||
"zh_TW": "調撥單",
|
|
||||||
"en": "Transfer Audit",
|
|
||||||
"ja": "転送監査"
|
|
||||||
},
|
|
||||||
"Transfers": {
|
|
||||||
"zh_TW": "調撥單",
|
|
||||||
"en": "Transfers",
|
|
||||||
"ja": "転送"
|
|
||||||
},
|
|
||||||
"Inventory Management": {
|
|
||||||
"zh_TW": "庫存管理",
|
|
||||||
"en": "Inventory Management",
|
|
||||||
"ja": "在庫管理"
|
|
||||||
},
|
|
||||||
"Manage stock levels, stock-in orders, and movement history": {
|
|
||||||
"zh_TW": "追蹤庫存水位、進貨單與異動紀錄",
|
|
||||||
"en": "Manage stock levels, stock-in orders, and movement history",
|
|
||||||
"ja": "在庫レベル、入庫伝票、および移動履歴の管理"
|
|
||||||
},
|
|
||||||
"Track stock levels, stock-in orders, and movement history": {
|
|
||||||
"zh_TW": "追蹤庫存水位、進貨單與異動紀錄",
|
|
||||||
"en": "Track stock levels, stock-in orders, and movement history",
|
|
||||||
"ja": "在庫レベル、入庫伝票、および移動履歴の追跡"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for lang, path in files.items():
|
|
||||||
if not os.path.exists(path):
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
# Update menu.warehouses
|
|
||||||
if lang == 'zh_TW':
|
|
||||||
data['menu.warehouses'] = "倉儲管理"
|
|
||||||
|
|
||||||
# Add/Update keys
|
|
||||||
for k, v in new_keys.items():
|
|
||||||
if lang in v:
|
|
||||||
data[k] = v[lang]
|
|
||||||
elif lang == 'en':
|
|
||||||
data[k] = k
|
|
||||||
|
|
||||||
# Sort keys and write back
|
|
||||||
with open(path, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(data, f, ensure_ascii=False, indent=4, sort_keys=True)
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Mocking Route and Laravel environment for breadcrumb logic test
|
|
||||||
|
|
||||||
function __($str) { return $str; }
|
|
||||||
|
|
||||||
class Route {
|
|
||||||
public static $currentName = '';
|
|
||||||
public static function currentRouteName() { return self::$currentName; }
|
|
||||||
}
|
|
||||||
|
|
||||||
function testBreadcrumbs($routeName) {
|
|
||||||
Route::$currentName = $routeName;
|
|
||||||
$links = [];
|
|
||||||
$links[] = [
|
|
||||||
'label' => __('Dashboard'),
|
|
||||||
'url' => '/admin/dashboard',
|
|
||||||
'active' => $routeName === 'admin.dashboard'
|
|
||||||
];
|
|
||||||
|
|
||||||
$moduleMap = [
|
|
||||||
'admin.warehouses' => __('Warehouse Management'),
|
|
||||||
// ... simplified
|
|
||||||
];
|
|
||||||
|
|
||||||
$foundModule = null;
|
|
||||||
foreach ($moduleMap as $prefix => $label) {
|
|
||||||
if (str_starts_with($routeName, $prefix)) {
|
|
||||||
$foundModule = [
|
|
||||||
'label' => $label,
|
|
||||||
'url' => '#',
|
|
||||||
'active' => false
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($foundModule) {
|
|
||||||
$links[] = $foundModule;
|
|
||||||
}
|
|
||||||
|
|
||||||
$segments = explode('.', $routeName);
|
|
||||||
$lastSegment = end($segments);
|
|
||||||
|
|
||||||
// Simplifed page labels
|
|
||||||
$pageLabel = match($lastSegment) {
|
|
||||||
'inventory' => __('Inventory Management'),
|
|
||||||
'index' => 'Warehouse Overview',
|
|
||||||
default => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($pageLabel) {
|
|
||||||
$links[] = [
|
|
||||||
'label' => $pageLabel,
|
|
||||||
'active' => true
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Route: $routeName\n";
|
|
||||||
foreach ($links as $link) {
|
|
||||||
echo " - " . $link['label'] . ($link['active'] ? " (active)" : "") . "\n";
|
|
||||||
}
|
|
||||||
echo "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
testBreadcrumbs('admin.warehouses.inventory');
|
|
||||||
testBreadcrumbs('admin.warehouses.index');
|
|
||||||
@ -61,6 +61,8 @@ class DiscordNotificationSecurityTest extends TestCase
|
|||||||
|
|
||||||
// 確保 tenantAdminTemplate 包含我們新加的 menu.basic.discord-notifications
|
// 確保 tenantAdminTemplate 包含我們新加的 menu.basic.discord-notifications
|
||||||
$tenantRole->syncPermissions($tenantAdminTemplate->permissions);
|
$tenantRole->syncPermissions($tenantAdminTemplate->permissions);
|
||||||
|
// 手動指派以測試有權限時的存取行為 (因現已改為比照機台設定預設不配給範本)
|
||||||
|
$tenantRole->givePermissionTo('menu.basic.discord-notifications');
|
||||||
$this->tenantAdmin->assignRole($tenantRole);
|
$this->tenantAdmin->assignRole($tenantRole);
|
||||||
|
|
||||||
// 5. 建立普通無此權限之帳號
|
// 5. 建立普通無此權限之帳號
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user