Merge branch 'main' into demo

This commit is contained in:
sky121113 2026-05-18 17:02:02 +08:00
commit 4c2b1e7a54
6 changed files with 72 additions and 12 deletions

View File

@ -1302,6 +1302,7 @@
"Remote dispense failed for slot :slot": "Remote dispense failed for slot :slot",
"Remote dispense successful for slot :slot": "Remote dispense successful for slot :slot",
"Removal": "Removal",
"Remove Assignment Confirmation": "Remove Assignment Confirmation",
"Repair": "Repair",
"Replenish": "Replenish",
"Replenishment": "Replenishment",

View File

@ -1302,6 +1302,7 @@
"Remote dispense failed for slot :slot": "スロット :slot の遠隔出庫に失敗しました",
"Remote dispense successful for slot :slot": "スロット :slot の遠隔出庫に成功しました",
"Removal": "撤去",
"Remove Assignment Confirmation": "割り当て解除の確認",
"Repair": "修理",
"Replenish": "補充",
"Replenishment": "補充",

View File

@ -1321,6 +1321,7 @@
"Remote dispense failed for slot :slot": "貨道 :slot 遠端出貨失敗",
"Remote dispense successful for slot :slot": "貨道 :slot 遠端出貨成功",
"Removal": "撤機",
"Remove Assignment Confirmation": "確認解除綁定",
"Repair": "維修",
"Replenish": "補貨",
"Replenishment": "補貨",

View File

@ -760,6 +760,17 @@ $baseRoute = 'admin.data-config.advertisements';
<x-status-confirm-modal :title="__('Disable Advertisement Confirmation')" :message="__('Are you sure you want to disable this advertisement?')" />
<x-confirm-modal
alpineVar="isAssignmentDeleteOpen"
confirmAction="submitRemoveAssignment()"
iconType="danger"
confirmColor="rose"
:title="__('Remove Assignment Confirmation')"
:message="__('Are you sure you want to remove this assignment?')"
:confirmText="__('Delete Permanently')"
isSubmittingVar="isSubmitting"
/>
<form x-ref="statusToggleForm" :action="toggleFormAction" method="POST" class="hidden">
@csrf
@method('PATCH')
@ -790,6 +801,10 @@ $baseRoute = 'admin.data-config.advertisements';
isDeleteConfirmOpen: false,
deleteFormAction: '',
// Assignment Delete Modal
isAssignmentDeleteOpen: false,
assignmentIdToDelete: null,
// Status Toggle
isStatusConfirmOpen: false,
toggleFormAction: '',
@ -1218,6 +1233,11 @@ $baseRoute = 'admin.data-config.advertisements';
},
async submitAssignment() {
if (this.isSubmitting) return;
this.isSubmitting = true;
const bar = document.getElementById('top-loading-bar');
if (bar) bar.classList.add('loading');
try {
const response = await fetch(this.urls.assign, {
method: 'POST',
@ -1232,7 +1252,7 @@ $baseRoute = 'admin.data-config.advertisements';
const result = await response.json();
if (response.ok && result.success) {
this.isAssignModalOpen = false;
this.fetchMachineAds();
await this.fetchMachineAds();
window.showToast?.(result.message, 'success');
} else {
let msg = result.message || 'Error';
@ -1244,6 +1264,9 @@ $baseRoute = 'admin.data-config.advertisements';
} catch (e) {
console.error('Failed to assign ad', e);
window.showToast?.('{{ __("System Error") }}', 'error');
} finally {
this.isSubmitting = false;
if (bar) bar.classList.remove('loading');
}
},
@ -1280,6 +1303,7 @@ $baseRoute = 'admin.data-config.advertisements';
async fetchMachineAds() {
const url = this.urls.getMachineAds.replace(':id', this.selectedMachineId);
this.isLoading = true;
try {
const response = await fetch(url);
const result = await response.json();
@ -1292,6 +1316,8 @@ $baseRoute = 'admin.data-config.advertisements';
}
} catch (e) {
console.error('Failed to fetch machine ads', e);
} finally {
this.isLoading = false;
}
},
@ -1406,8 +1432,20 @@ $baseRoute = 'admin.data-config.advertisements';
});
},
async removeAssignment(id) {
if (!confirm("{{ __('Are you sure you want to remove this assignment?') }}")) return;
removeAssignment(id) {
this.assignmentIdToDelete = id;
this.isAssignmentDeleteOpen = true;
},
async submitRemoveAssignment() {
const id = this.assignmentIdToDelete;
if (!id) return;
if (this.isSubmitting) return;
this.isSubmitting = true;
const bar = document.getElementById('top-loading-bar');
if (bar) bar.classList.add('loading');
const url = this.urls.removeAssignment.replace(':id', id);
try {
@ -1420,13 +1458,19 @@ $baseRoute = 'admin.data-config.advertisements';
});
const result = await response.json();
if (result.success) {
this.fetchMachineAds();
this.isAssignmentDeleteOpen = false;
await this.fetchMachineAds();
window.showToast?.(result.message, 'success');
} else {
window.showToast?.(result.message || 'Error', 'error');
}
} catch (e) {
console.error('Failed to remove assignment', e);
} finally {
this.assignmentIdToDelete = null;
this.isAssignmentDeleteOpen = false;
this.isSubmitting = false;
if (bar) bar.classList.remove('loading');
}
},

View File

@ -8,7 +8,7 @@
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0">
<div class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm" @click="isAssignModalOpen = false"></div>
<div class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm" @click="!isSubmitting && (isAssignModalOpen = false)"></div>
<div class="flex items-center justify-center min-h-screen p-4 sm:p-0">
<div class="relative inline-block px-8 py-10 text-left align-bottom transition-all transform luxury-card rounded-3xl dark:bg-slate-900 border-slate-200/50 dark:border-slate-700/50 shadow-2xl sm:my-8 sm:align-middle sm:max-w-lg sm:w-full overflow-visible animate-luxury-in">
@ -19,7 +19,7 @@
<h3 class="text-2xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ __('Assign Advertisement') }}</h3>
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1">{{ __('Select a material to play on this machine') }}</p>
</div>
<button @click="isAssignModalOpen = false" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors">
<button @click="!isSubmitting && (isAssignModalOpen = false)" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors" :disabled="isSubmitting">
<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.5" d="M6 18L18 6M6 6l12 12" /></svg>
</button>
</div>
@ -44,11 +44,15 @@
<!-- Action Footer -->
<div class="flex items-center justify-end gap-3 pt-6">
<button type="button" @click="isAssignModalOpen = false" class="px-6 py-3 text-sm font-black text-slate-500 hover:text-slate-700 dark:hover:text-slate-300 transition-colors uppercase tracking-widest">
<button type="button" @click="isAssignModalOpen = false" :disabled="isSubmitting" class="px-6 py-3 text-sm font-black text-slate-500 hover:text-slate-700 dark:hover:text-slate-300 transition-colors uppercase tracking-widest disabled:opacity-60 disabled:cursor-not-allowed">
{{ __('Cancel') }}
</button>
<button type="submit" class="btn-luxury-primary px-10 py-3">
{{ __('Confirm Assignment') }}
<button type="submit" :disabled="isSubmitting" class="btn-luxury-primary px-10 py-3 flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed">
<svg x-show="isSubmitting" x-cloak class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>{{ __('Confirm Assignment') }}</span>
</button>
</div>
</form>

View File

@ -7,6 +7,7 @@
'confirmText' => __('Confirm'),
'cancelText' => __('Cancel'),
'confirmColor' => 'sky', // sky, rose, amber, emerald
'isSubmittingVar' => null, // Optional Alpine variable for submitting state
])
@php
@ -77,11 +78,19 @@
</div>
<div class="mt-8 sm:mt-10 sm:flex sm:flex-row-reverse gap-3">
<button type="button" @click="{{ $confirmAction }}"
class="inline-flex justify-center w-full px-6 py-3 text-sm font-black text-white transition-all rounded-xl shadow-lg dark:shadow-none hover:scale-[1.02] active:scale-[0.98] sm:w-auto uppercase tracking-widest font-display {{ $btnClasses }}">
{{ $confirmText }}
@if($isSubmittingVar) :disabled="{{ $isSubmittingVar }}" @endif
class="inline-flex items-center justify-center gap-2 w-full px-6 py-3 text-sm font-black text-white transition-all rounded-xl shadow-lg dark:shadow-none hover:scale-[1.02] active:scale-[0.98] sm:w-auto uppercase tracking-widest font-display {{ $btnClasses }} disabled:opacity-60 disabled:cursor-not-allowed">
@if($isSubmittingVar)
<svg x-show="{{ $isSubmittingVar }}" x-cloak class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
@endif
<span>{{ $confirmText }}</span>
</button>
<button type="button" @click="{{ $alpineVar }} = false"
class="inline-flex justify-center w-full px-6 py-3 mt-3 text-sm font-black text-slate-700 dark:text-slate-200 transition-all bg-slate-100 dark:bg-slate-800 rounded-xl hover:bg-slate-200 dark:hover:bg-slate-700 sm:mt-0 sm:w-auto uppercase tracking-widest font-display">
@if($isSubmittingVar) :disabled="{{ $isSubmittingVar }}" @endif
class="inline-flex justify-center w-full px-6 py-3 mt-3 text-sm font-black text-slate-700 dark:text-slate-200 transition-all bg-slate-100 dark:bg-slate-800 rounded-xl hover:bg-slate-200 dark:hover:bg-slate-700 sm:mt-0 sm:w-auto uppercase tracking-widest font-display disabled:opacity-60 disabled:cursor-not-allowed">
{{ $cancelText }}
</button>
</div>