[FEAT] 優化廣告同步與指令中心 UI
1. 廣告管理:將原生 confirm 替換為極簡奢華風 Alpine.js 彈窗,並新增「操作備註」欄位與「目標機台」顯示。 2. 指令中心:在指令確認彈窗中新增「目標機台」顯示,並優化 UI 佈局。 3. 多語系支援:新增 Sync Ads (同步廣告) 至 zh_TW, en, ja 語系檔,並應用於指令中心與篩選列。 4. 後端邏輯:更新 AdvertisementController,將操作備註正確存入 RemoteCommand 的 remark 欄位。 5. 指令規範:同步更新 MQTT 通訊規範 (SKILL.md) 與 API 文件 (api-docs.php),加入 update_ads 指令說明。
This commit is contained in:
parent
7d3e25d090
commit
ce87f453a2
@ -279,6 +279,7 @@ Mqtt3Client client = MqttClient.builder()
|
|||||||
- `change`: 遠端找零(夾帶 amount 參數)。
|
- `change`: 遠端找零(夾帶 amount 參數)。
|
||||||
- `update_inventory`: 遠端即時修改庫存。
|
- `update_inventory`: 遠端即時修改庫存。
|
||||||
- `dispense`: 遠端出貨指令。
|
- `dispense`: 遠端出貨指令。
|
||||||
|
- `update_ads`: 遠端廣告同步指令。
|
||||||
|
|
||||||
**`update_inventory` Payload 範例:**
|
**`update_inventory` Payload 範例:**
|
||||||
```json
|
```json
|
||||||
|
|||||||
@ -282,4 +282,64 @@ class AdvertisementController extends AdminController
|
|||||||
'message' => __('Assignment removed successfully.')
|
'message' => __('Assignment removed successfully.')
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步廣告至機台 (透過 MQTT 發送 update_ads 指令)
|
||||||
|
*/
|
||||||
|
public function syncToMachine(Request $request, Machine $machine, \App\Services\Machine\MqttService $mqttService)
|
||||||
|
{
|
||||||
|
// 併行檢查:若有 pending 指令,超過 1 分鐘視為逾時可覆蓋
|
||||||
|
$pendingCommand = \App\Models\Machine\RemoteCommand::where('machine_id', $machine->id)
|
||||||
|
->where('command_type', 'update_ads')
|
||||||
|
->where('status', 'pending')
|
||||||
|
->latest()
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($pendingCommand) {
|
||||||
|
$isTimedOut = $pendingCommand->created_at->lt(now()->subMinute());
|
||||||
|
|
||||||
|
if (!$isTimedOut) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => __('This machine has a pending command. Please wait.')
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 超過 1 分鐘:視為逾時,自動標記舊指令
|
||||||
|
$pendingCommand->update([
|
||||||
|
'status' => 'timeout',
|
||||||
|
'note' => __('Superseded by new command (Timeout)'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = \App\Models\Machine\RemoteCommand::create([
|
||||||
|
'machine_id' => $machine->id,
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'command_type' => 'update_ads',
|
||||||
|
'payload' => [],
|
||||||
|
'status' => 'pending',
|
||||||
|
'remark' => $request->filled('note') ? $request->input('note') : __('Manual Sync Ads'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$success = $mqttService->pushCommand(
|
||||||
|
$machine->serial_no,
|
||||||
|
'update_ads',
|
||||||
|
[],
|
||||||
|
(string) $command->id
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => __('Sync command sent successfully.')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$command->update(['status' => 'failed', 'note' => 'MQTT push failed']);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => __('Failed to send sync command.')
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -674,6 +674,24 @@ return [
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'name' => '指令下發:廣告同步 (Update Ads)',
|
||||||
|
'slug' => 'mqtt-command-update-ads',
|
||||||
|
'action' => 'SUB',
|
||||||
|
'topic' => 'machine/{serial_no}/command',
|
||||||
|
'qos' => 1,
|
||||||
|
'description' => '雲端主動下發「廣告同步」指令。指示機台重新拉取 B005 廣告清單。',
|
||||||
|
'payload_parameters' => [
|
||||||
|
'command' => ['type' => 'string', 'description' => '固定為 "update_ads"'],
|
||||||
|
'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'],
|
||||||
|
'payload' => ['type' => 'object', 'description' => '空物件 (無額外參數)'],
|
||||||
|
],
|
||||||
|
'payload_example' => [
|
||||||
|
'command' => 'update_ads',
|
||||||
|
'command_id' => '1122334456',
|
||||||
|
'payload' => new \stdClass()
|
||||||
|
],
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'name' => '指令下發:遠端找零 (Change)',
|
'name' => '指令下發:遠端找零 (Change)',
|
||||||
'slug' => 'mqtt-command-change',
|
'slug' => 'mqtt-command-change',
|
||||||
|
|||||||
@ -1943,5 +1943,6 @@
|
|||||||
"Total Orders": "Total Orders",
|
"Total Orders": "Total Orders",
|
||||||
"Real-time fleet status and revenue monitoring": "Real-time fleet status and revenue monitoring",
|
"Real-time fleet status and revenue monitoring": "Real-time fleet status and revenue monitoring",
|
||||||
"[StaffCard] Verification failed: :uid": "[StaffCard] Verification failed: :uid",
|
"[StaffCard] Verification failed: :uid": "[StaffCard] Verification failed: :uid",
|
||||||
"[PickupCode] Verification failed: :code": "[PickupCode] Verification failed: :code"
|
"[PickupCode] Verification failed: :code": "[PickupCode] Verification failed: :code",
|
||||||
|
"Sync Ads": "Sync Ads"
|
||||||
}
|
}
|
||||||
@ -1942,5 +1942,6 @@
|
|||||||
"Total Orders": "本日の合計注文",
|
"Total Orders": "本日の合計注文",
|
||||||
"Real-time fleet status and revenue monitoring": "フリート全体のリアルタイムステータスと収益監視",
|
"Real-time fleet status and revenue monitoring": "フリート全体のリアルタイムステータスと収益監視",
|
||||||
"[StaffCard] Verification failed: :uid": "[スタッフカード] 認証失敗: :uid",
|
"[StaffCard] Verification failed: :uid": "[スタッフカード] 認証失敗: :uid",
|
||||||
"[PickupCode] Verification failed: :code": "[受取コード] 認証失敗: :code"
|
"[PickupCode] Verification failed: :code": "[受取コード] 認証失敗: :code",
|
||||||
|
"Sync Ads": "広告を同期"
|
||||||
}
|
}
|
||||||
@ -1591,6 +1591,7 @@
|
|||||||
"This is a system administrator role. Its name is locked to ensure system stability.": "這是系統管理員角色,名稱已鎖定以確保系統穩定性。",
|
"This is a system administrator role. Its name is locked to ensure system stability.": "這是系統管理員角色,名稱已鎖定以確保系統穩定性。",
|
||||||
"This role belongs to another company and cannot be assigned.": "此角色屬於其他公司,無法指派。",
|
"This role belongs to another company and cannot be assigned.": "此角色屬於其他公司,無法指派。",
|
||||||
"This slot has a pending command. Please wait.": "此貨道已有指令正在執行,請稍後。",
|
"This slot has a pending command. Please wait.": "此貨道已有指令正在執行,請稍後。",
|
||||||
|
"This machine has a pending command. Please wait.": "此機台已有指令正在執行,請稍後。",
|
||||||
"This slot has a pending update. Please wait for the previous command to complete.": "此貨道已有更新指令在執行中,請等候前一個指令完成。",
|
"This slot has a pending update. Please wait for the previous command to complete.": "此貨道已有更新指令在執行中,請等候前一個指令完成。",
|
||||||
"This slot is syncing with the machine. Please wait.": "此貨道正在與機台同步中,請稍候。",
|
"This slot is syncing with the machine. Please wait.": "此貨道正在與機台同步中,請稍候。",
|
||||||
"Ticket": "憑證",
|
"Ticket": "憑證",
|
||||||
@ -1943,5 +1944,10 @@
|
|||||||
"Total Orders": "今日訂單",
|
"Total Orders": "今日訂單",
|
||||||
"Real-time fleet status and revenue monitoring": "全機台即時狀態與營收監控",
|
"Real-time fleet status and revenue monitoring": "全機台即時狀態與營收監控",
|
||||||
"[StaffCard] Verification failed: :uid": "[員工卡] 驗證失敗: :uid",
|
"[StaffCard] Verification failed: :uid": "[員工卡] 驗證失敗: :uid",
|
||||||
"[PickupCode] Verification failed: :code": "[取貨碼] 驗證失敗: :code"
|
"[PickupCode] Verification failed: :code": "[取貨碼] 驗證失敗: :code",
|
||||||
|
"Sync to Machine": "同步廣告至機台",
|
||||||
|
"Manual Sync Ads": "手動同步機台廣告",
|
||||||
|
"Sync command sent successfully.": "同步指令已成功送出",
|
||||||
|
"Failed to send sync command.": "無法送出同步指令",
|
||||||
|
"Sync Ads": "同步廣告"
|
||||||
}
|
}
|
||||||
@ -22,7 +22,8 @@ $baseRoute = 'admin.data-config.advertisements';
|
|||||||
"getMachineAds": "{{ route($baseRoute . ".machine.get", ":id") }}",
|
"getMachineAds": "{{ route($baseRoute . ".machine.get", ":id") }}",
|
||||||
"assign": "{{ route($baseRoute . ".assign") }}",
|
"assign": "{{ route($baseRoute . ".assign") }}",
|
||||||
"reorder": "{{ route($baseRoute . ".assignments.reorder") }}",
|
"reorder": "{{ route($baseRoute . ".assignments.reorder") }}",
|
||||||
"removeAssignment": "{{ route($baseRoute . ".assignment.remove", ":id") }}"
|
"removeAssignment": "{{ route($baseRoute . ".assignment.remove", ":id") }}",
|
||||||
|
"syncToMachine": "{{ route($baseRoute . ".machine.sync", ":id") }}"
|
||||||
}'>
|
}'>
|
||||||
|
|
||||||
<x-luxury-spinner show="isLoading" />
|
<x-luxury-spinner show="isLoading" />
|
||||||
@ -369,13 +370,26 @@ $baseRoute = 'admin.data-config.advertisements';
|
|||||||
<div x-show="activeTab === 'machine'" class="space-y-6" x-cloak>
|
<div x-show="activeTab === 'machine'" class="space-y-6" x-cloak>
|
||||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||||
<!-- Machine Filter -->
|
<!-- Machine Filter -->
|
||||||
<div class="max-w-md mx-auto mb-10">
|
<div class="max-w-3xl mx-auto mb-10">
|
||||||
<label
|
<label
|
||||||
class="block text-sm font-black text-slate-700 dark:text-slate-200 mb-3 text-center uppercase tracking-widest">{{
|
class="block text-sm font-black text-slate-700 dark:text-slate-200 mb-3 text-center uppercase tracking-widest">{{
|
||||||
__('Please select a machine first') }}</label>
|
__('Please select a machine first') }}</label>
|
||||||
<x-searchable-select name="machine_selector"
|
<div class="flex flex-col sm:flex-row items-center gap-4 justify-center">
|
||||||
:options="$machines->map(fn($m) => (object)['id' => $m->id, 'name' => $m->name . ' (' . $m->serial_no . ')'])"
|
<div class="w-full sm:w-[400px]">
|
||||||
placeholder="{{ __('Search Machine...') }}" @change="selectMachine($event.target.value)" />
|
<x-searchable-select name="machine_selector"
|
||||||
|
:options="$machines->map(fn($m) => (object)['id' => $m->id, 'name' => $m->name . ' (' . $m->serial_no . ')'])"
|
||||||
|
placeholder="{{ __('Search Machine...') }}" @change="selectMachine($event.target.value)" />
|
||||||
|
</div>
|
||||||
|
<div x-show="selectedMachineId" x-transition class="shrink-0 w-full sm:w-auto" x-cloak>
|
||||||
|
<button @click="syncAdsToMachine()" type="button"
|
||||||
|
class="w-full sm:w-auto btn-luxury-primary h-[42px] whitespace-nowrap flex items-center justify-center gap-2 transition-all duration-300">
|
||||||
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||||
|
</svg>
|
||||||
|
<span class="text-sm font-bold uppercase tracking-widest">{{ __('Sync to Machine') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div x-show="selectedMachineId" class="animate-luxury-in">
|
<div x-show="selectedMachineId" class="animate-luxury-in">
|
||||||
@ -509,6 +523,76 @@ $baseRoute = 'admin.data-config.advertisements';
|
|||||||
@include('admin.ads.partials.ad-modal')
|
@include('admin.ads.partials.ad-modal')
|
||||||
@include('admin.ads.partials.assign-modal')
|
@include('admin.ads.partials.assign-modal')
|
||||||
|
|
||||||
|
<!-- Sync Confirmation Modal -->
|
||||||
|
<template x-teleport="body">
|
||||||
|
<div x-show="isSyncConfirmOpen" class="fixed inset-0 z-[100] overflow-y-auto" x-cloak>
|
||||||
|
<div class="flex min-h-screen items-center justify-center p-4 text-center sm:p-0">
|
||||||
|
<!-- Background Backdrop -->
|
||||||
|
<div x-show="isSyncConfirmOpen" 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/60 backdrop-blur-sm transition-opacity"
|
||||||
|
@click="isSyncConfirmOpen = false"></div>
|
||||||
|
|
||||||
|
<!-- Modal Content -->
|
||||||
|
<div x-show="isSyncConfirmOpen" 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 transform overflow-hidden rounded-[2.5rem] bg-white dark:bg-slate-900 p-8 text-left shadow-2xl transition-all sm:my-8 sm:w-full sm:max-w-lg border border-slate-200 dark:border-slate-800">
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4 mb-6">
|
||||||
|
<div
|
||||||
|
class="w-14 h-14 rounded-2xl bg-amber-500/10 flex items-center justify-center text-amber-500 border border-amber-500/20">
|
||||||
|
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight uppercase">{{
|
||||||
|
__('Command Confirmation') }}</h3>
|
||||||
|
<p class="text-xs font-bold text-slate-400 uppercase tracking-widest mt-0.5">{{ __('Please confirm the details below') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4 bg-slate-50 dark:bg-slate-950/50 p-6 rounded-3xl border border-slate-100 dark:border-slate-800/50 mb-8">
|
||||||
|
<div class="flex justify-between items-center px-1">
|
||||||
|
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Command Type') }}</span>
|
||||||
|
<span class="text-sm font-black text-slate-800 dark:text-slate-200">{{ __('Sync to Machine') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center px-1 pt-3 border-t border-slate-200/50 dark:border-slate-800/50">
|
||||||
|
<span class="text-[10px] font-black text-cyan-500 uppercase tracking-widest">{{ __('Target Machine') }}</span>
|
||||||
|
<span class="text-sm font-black text-slate-800 dark:text-slate-200" x-text="machines.find(m => m.id == selectedMachineId)?.name || ''"></span>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2 px-1 pt-3 border-t border-slate-200/50 dark:border-slate-800/50">
|
||||||
|
<label
|
||||||
|
class="text-[10px] font-black text-slate-500 dark:text-slate-400 uppercase tracking-[0.2em] ml-1">{{
|
||||||
|
__('Operation Note') }}</label>
|
||||||
|
<textarea x-model="syncNote"
|
||||||
|
class="luxury-input w-full min-h-[100px] text-sm py-3 px-4 bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-800 focus:border-cyan-500/50 rounded-2xl"
|
||||||
|
placeholder="{{ __('Reason for this command...') }}"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<button @click="isSyncConfirmOpen = false"
|
||||||
|
class="flex-1 px-6 py-4 rounded-2xl bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 text-xs font-black uppercase tracking-widest hover:bg-slate-200 dark:hover:bg-slate-700 transition-all">
|
||||||
|
{{ __('Cancel') }}
|
||||||
|
</button>
|
||||||
|
<button @click="executeSyncAds()"
|
||||||
|
class="flex-1 px-6 py-4 rounded-2xl bg-cyan-600 text-white text-xs font-black uppercase tracking-widest hover:bg-cyan-500 shadow-lg shadow-cyan-500/20 active:scale-[0.98] transition-all">
|
||||||
|
{{ __('Execute') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Preview Modal -->
|
<!-- Preview Modal -->
|
||||||
<div x-show="isPreviewOpen" class="fixed inset-0 z-[120] flex items-center justify-center p-4 sm:p-6" x-cloak
|
<div x-show="isPreviewOpen" class="fixed inset-0 z-[120] flex items-center justify-center p-4 sm:p-6" x-cloak
|
||||||
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0"
|
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0"
|
||||||
@ -686,6 +770,10 @@ $baseRoute = 'admin.data-config.advertisements';
|
|||||||
position: ''
|
position: ''
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Sync Confirmation
|
||||||
|
isSyncConfirmOpen: false,
|
||||||
|
syncNote: '',
|
||||||
|
|
||||||
// Sequence Preview
|
// Sequence Preview
|
||||||
isSequencePreviewOpen: false,
|
isSequencePreviewOpen: false,
|
||||||
sequenceAds: [],
|
sequenceAds: [],
|
||||||
@ -751,6 +839,44 @@ $baseRoute = 'admin.data-config.advertisements';
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
syncAdsToMachine() {
|
||||||
|
if (!this.selectedMachineId) {
|
||||||
|
window.showToast?.('{{ __('Please select a machine first') }}', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isSyncConfirmOpen = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
async executeSyncAds() {
|
||||||
|
this.isSyncConfirmOpen = false;
|
||||||
|
try {
|
||||||
|
const url = this.urls.syncToMachine.replace(':id', this.selectedMachineId);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('note', this.syncNote);
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
window.showToast?.(result.message || '指令已發送', 'success');
|
||||||
|
} else {
|
||||||
|
window.showToast?.(result.message || '發送失敗', 'error');
|
||||||
|
}
|
||||||
|
this.syncNote = ''; // reset note on finish
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error syncing ads:', error);
|
||||||
|
window.showToast?.('發送失敗,請重試', 'error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
startSequencePreview(pos) {
|
startSequencePreview(pos) {
|
||||||
if (!this.machineAds[pos] || this.machineAds[pos].length === 0) return;
|
if (!this.machineAds[pos] || this.machineAds[pos].length === 0) return;
|
||||||
this.sequenceAds = this.machineAds[pos];
|
this.sequenceAds = this.machineAds[pos];
|
||||||
|
|||||||
@ -41,6 +41,7 @@
|
|||||||
'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'),
|
||||||
|
'Sync Ads' => __('Sync Ads'),
|
||||||
'Pending' => __('Pending'),
|
'Pending' => __('Pending'),
|
||||||
'Sent' => __('Sent'),
|
'Sent' => __('Sent'),
|
||||||
'Success' => __('Success'),
|
'Success' => __('Success'),
|
||||||
@ -441,7 +442,8 @@
|
|||||||
'unlock': this.translations['Lock Page Unlock'],
|
'unlock': this.translations['Lock Page Unlock'],
|
||||||
'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'],
|
||||||
|
'update_ads': this.translations['Sync Ads']
|
||||||
};
|
};
|
||||||
return names[type] || type;
|
return names[type] || type;
|
||||||
},
|
},
|
||||||
@ -951,7 +953,12 @@
|
|||||||
<span class="text-sm font-black text-slate-800 dark:text-slate-200"
|
<span class="text-sm font-black text-slate-800 dark:text-slate-200"
|
||||||
x-text="getCommandName(confirmModal.type)"></span>
|
x-text="getCommandName(confirmModal.type)"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2 px-1">
|
<div class="flex justify-between items-center px-1 pt-3 border-t border-slate-200/50 dark:border-slate-800/50">
|
||||||
|
<span class="text-[10px] font-black text-cyan-500 uppercase tracking-widest">{{ __('Target Machine') }}</span>
|
||||||
|
<span class="text-sm font-black text-slate-800 dark:text-slate-200"
|
||||||
|
x-text="selectedMachine ? selectedMachine.name : ''"></span>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2 px-1 pt-3 border-t border-slate-200/50 dark:border-slate-800/50">
|
||||||
<label
|
<label
|
||||||
class="text-[10px] font-black text-slate-500 dark:text-slate-400 uppercase tracking-[0.2em] ml-1">{{
|
class="text-[10px] font-black text-slate-500 dark:text-slate-400 uppercase tracking-[0.2em] ml-1">{{
|
||||||
__('Operation Note') }}</label>
|
__('Operation Note') }}</label>
|
||||||
|
|||||||
@ -83,6 +83,7 @@
|
|||||||
'unlock' => __('Lock Page Unlock'),
|
'unlock' => __('Lock Page Unlock'),
|
||||||
'change' => __('Remote Change'),
|
'change' => __('Remote Change'),
|
||||||
'dispense' => __('Remote Dispense'),
|
'dispense' => __('Remote Dispense'),
|
||||||
|
'update_ads' => __('Sync Ads'),
|
||||||
]" :selected="request('command_type')" :placeholder="__('All Command Types')"
|
]" :selected="request('command_type')" :placeholder="__('All Command Types')"
|
||||||
:hasSearch="false"
|
:hasSearch="false"
|
||||||
@change="searchInTab('history')" />
|
@change="searchInTab('history')" />
|
||||||
|
|||||||
@ -173,6 +173,7 @@ Route::middleware(['auth', 'verified', 'tenant.access'])->prefix('admin')->name(
|
|||||||
Route::post('/advertisements/assign', [App\Http\Controllers\Admin\AdvertisementController::class, 'assign'])->name('advertisements.assign');
|
Route::post('/advertisements/assign', [App\Http\Controllers\Admin\AdvertisementController::class, 'assign'])->name('advertisements.assign');
|
||||||
Route::post('/advertisements/assignments/reorder', [App\Http\Controllers\Admin\AdvertisementController::class, 'reorderAssignments'])->name('advertisements.assignments.reorder');
|
Route::post('/advertisements/assignments/reorder', [App\Http\Controllers\Admin\AdvertisementController::class, 'reorderAssignments'])->name('advertisements.assignments.reorder');
|
||||||
Route::delete('/advertisements/assignment/{id}', [App\Http\Controllers\Admin\AdvertisementController::class, 'removeAssignment'])->name('advertisements.assignment.remove');
|
Route::delete('/advertisements/assignment/{id}', [App\Http\Controllers\Admin\AdvertisementController::class, 'removeAssignment'])->name('advertisements.assignment.remove');
|
||||||
|
Route::post('/advertisements/machine/{machine}/sync', [App\Http\Controllers\Admin\AdvertisementController::class, 'syncToMachine'])->name('advertisements.machine.sync');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/sub-accounts', [App\Http\Controllers\Admin\PermissionController::class, 'accounts'])->name('sub-accounts')->middleware('can:menu.data-config.sub-accounts');
|
Route::get('/sub-accounts', [App\Http\Controllers\Admin\PermissionController::class, 'accounts'])->name('sub-accounts')->middleware('can:menu.data-config.sub-accounts');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user