[FEAT] 實作機台序號編輯功能與多語系支援
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 49s
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 49s
1. 更新機台控制器 (MachineController),在更新時執行 serial_no 的必填與唯一性驗證。 2. 修改機台管理首頁 (index.blade.php),在快速編輯彈窗中加入機台序號輸入欄位。 3. 修正基礎設定中機台編輯頁面 (edit.blade.php),將原本唯讀的機台序號欄位改為可編輯輸入框,並加入必填標記。 4. 補齊並統一繁體中文、英文、日文翻譯檔中關於「機台序號」的翻譯 Key。
This commit is contained in:
@@ -463,6 +463,7 @@
|
||||
"Machine Model Settings": "Machine Model Settings",
|
||||
"Machine model updated successfully.": "Machine model updated successfully.",
|
||||
"Machine Name": "Machine Name",
|
||||
"Machine Serial No": "Machine Serial No",
|
||||
"Machine Permissions": "Machine Permissions",
|
||||
"Machine Reboot": "Machine Reboot",
|
||||
"Machine Registry": "Machine Registry",
|
||||
|
||||
@@ -463,6 +463,7 @@
|
||||
"Machine Model Settings": "機台型號設定",
|
||||
"Machine model updated successfully.": "機台型號已成功更新。",
|
||||
"Machine Name": "機台名稱",
|
||||
"Machine Serial No": "機台序號",
|
||||
"Machine Permissions": "機台權限",
|
||||
"Machine Reboot": "機台重啟",
|
||||
"Machine Registry": "機台清冊",
|
||||
|
||||
@@ -102,8 +102,8 @@
|
||||
<input type="text" name="name" value="{{ old('name', $machine->name) }}" class="luxury-input w-full" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Serial Number') }}</label>
|
||||
<input type="text" value="{{ $machine->serial_no }}" class="luxury-input w-full bg-slate-50/50 dark:bg-slate-900/50 text-slate-400 cursor-not-allowed" readonly>
|
||||
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Machine Serial No') }} <span class="text-rose-500">*</span></label>
|
||||
<input type="text" name="serial_no" value="{{ old('serial_no', $machine->serial_no) }}" class="luxury-input w-full" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Location') }}</label>
|
||||
|
||||
@@ -118,7 +118,8 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="space-y-4 pb-20 mt-4" x-data="machineApp()" @keydown.escape.window="showLogPanel = false; showInventoryPanel = false">
|
||||
<div class="space-y-4 pb-20 mt-4" x-data="machineApp()"
|
||||
@keydown.escape.window="showLogPanel = false; showInventoryPanel = false">
|
||||
<!-- Top Header & Actions -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
@@ -566,20 +567,21 @@
|
||||
</div><!-- /Offcanvas -->
|
||||
|
||||
<!-- Edit Machine Name Modal -->
|
||||
<div x-show="showEditModal" class="fixed inset-0 z-[100] overflow-y-auto" style="display: none;" role="dialog" aria-modal="true">
|
||||
<div x-show="showEditModal" class="fixed inset-0 z-[100] overflow-y-auto" style="display: none;" role="dialog"
|
||||
aria-modal="true">
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||
<!-- Background Backdrop -->
|
||||
<div x-show="showEditModal" 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="showEditModal = false">
|
||||
class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm transition-opacity"
|
||||
@click="showEditModal = false">
|
||||
</div>
|
||||
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:min-h-screen" aria-hidden="true">​</span>
|
||||
|
||||
<!-- Modal Panel -->
|
||||
<div x-show="showEditModal"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
<div x-show="showEditModal" 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"
|
||||
@@ -588,15 +590,20 @@
|
||||
class="inline-block align-bottom bg-white dark:bg-slate-900 rounded-[2.5rem] p-10 text-left overflow-hidden shadow-2xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full border border-slate-100 dark:border-slate-800">
|
||||
|
||||
<div>
|
||||
<h3 class="text-2xl font-black text-slate-800 dark:text-white font-display tracking-tight leading-none mb-2">{{ __('Edit Machine Name') }}</h3>
|
||||
<p class="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Update identification for your asset') }}</p>
|
||||
<h3
|
||||
class="text-2xl font-black text-slate-800 dark:text-white font-display tracking-tight leading-none mb-2">
|
||||
{{ __('Edit Machine Name') }}</h3>
|
||||
<p class="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{
|
||||
__('Update identification for your asset') }}</p>
|
||||
|
||||
<form :action="'/admin/machines/' + editMachineId" method="POST" class="mt-8 space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="space-y-4">
|
||||
<label class="block text-xs font-black text-slate-500 dark:text-slate-400 uppercase tracking-[0.1em]">{{ __('New Machine Name') }}</label>
|
||||
<label
|
||||
class="block text-xs font-black text-slate-500 dark:text-slate-400 uppercase tracking-[0.1em]">{{
|
||||
__('New Machine Name') }}</label>
|
||||
<input type="text" name="name" x-model="editMachineName" required
|
||||
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...') }}">
|
||||
@@ -623,10 +630,12 @@
|
||||
aria-labelledby="inventory-panel-title" role="dialog" aria-modal="true">
|
||||
|
||||
<!-- Background backdrop -->
|
||||
<div x-show="showInventoryPanel" x-transition:enter="ease-in-out duration-300" x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100" x-transition:leave="ease-in-out duration-300"
|
||||
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
|
||||
class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm transition-opacity" @click="showInventoryPanel = false">
|
||||
<div x-show="showInventoryPanel" x-transition:enter="ease-in-out duration-300"
|
||||
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="ease-in-out duration-300" x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm transition-opacity"
|
||||
@click="showInventoryPanel = false">
|
||||
</div>
|
||||
|
||||
<div class="fixed inset-y-0 right-0 max-w-full flex">
|
||||
@@ -677,17 +686,26 @@
|
||||
|
||||
<!-- 統計摘要 -->
|
||||
<div class="mt-6 flex items-center gap-4">
|
||||
<div class="px-5 py-3 rounded-2xl bg-white dark:bg-slate-800/50 flex flex-col items-center min-w-[100px] border border-slate-100 dark:border-slate-800/50">
|
||||
<span class="text-[9px] font-black text-slate-400 uppercase tracking-widest mb-0.5">{{ __('Total Slots') }}</span>
|
||||
<span class="text-2xl font-black text-slate-700 dark:text-slate-200" x-text="inventorySlots.length"></span>
|
||||
<div
|
||||
class="px-5 py-3 rounded-2xl bg-white dark:bg-slate-800/50 flex flex-col items-center min-w-[100px] border border-slate-100 dark:border-slate-800/50">
|
||||
<span class="text-[9px] font-black text-slate-400 uppercase tracking-widest mb-0.5">{{
|
||||
__('Total Slots') }}</span>
|
||||
<span class="text-2xl font-black text-slate-700 dark:text-slate-200"
|
||||
x-text="inventorySlots.length"></span>
|
||||
</div>
|
||||
<div class="px-5 py-3 rounded-2xl bg-rose-500/5 border border-rose-500/10 flex flex-col items-center min-w-[100px]">
|
||||
<span class="text-[9px] font-black text-rose-500 uppercase tracking-widest mb-0.5">{{ __('Low Stock') }}</span>
|
||||
<span class="text-2xl font-black text-rose-600" x-text="inventorySlots.filter(s => s != null && s.stock <= 5).length"></span>
|
||||
<div
|
||||
class="px-5 py-3 rounded-2xl bg-rose-500/5 border border-rose-500/10 flex flex-col items-center min-w-[100px]">
|
||||
<span class="text-[9px] font-black text-rose-500 uppercase tracking-widest mb-0.5">{{
|
||||
__('Low Stock') }}</span>
|
||||
<span class="text-2xl font-black text-rose-600"
|
||||
x-text="inventorySlots.filter(s => s != null && s.stock <= 5).length"></span>
|
||||
</div>
|
||||
<div class="px-5 py-3 rounded-2xl bg-amber-500/5 border border-amber-500/10 flex flex-col items-center min-w-[100px]">
|
||||
<span class="text-[9px] font-black text-amber-500 uppercase tracking-widest mb-0.5">{{ __('Expiring') }}</span>
|
||||
<span class="text-2xl font-black text-amber-600" x-text="inventorySlots.filter(s => { if (!s || !s.expiry_date) return false; const diff = Math.round((new Date(s.expiry_date) - new Date()) / 86400000); return diff >= 0 && diff <= 7; }).length"></span>
|
||||
<div
|
||||
class="px-5 py-3 rounded-2xl bg-amber-500/5 border border-amber-500/10 flex flex-col items-center min-w-[100px]">
|
||||
<span class="text-[9px] font-black text-amber-500 uppercase tracking-widest mb-0.5">{{
|
||||
__('Expiring') }}</span>
|
||||
<span class="text-2xl font-black text-amber-600"
|
||||
x-text="inventorySlots.filter(s => { if (!s || !s.expiry_date) return false; const diff = Math.round((new Date(s.expiry_date) - new Date()) / 86400000); return diff >= 0 && diff <= 7; }).length"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /Header -->
|
||||
@@ -711,31 +729,42 @@
|
||||
<div class="flex items-center gap-6 mb-6" x-show="!inventoryLoading">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-full bg-rose-500 shadow-lg shadow-rose-500/30"></span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-[0.15em]">{{ __('Expired') }}</span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-[0.15em]">{{
|
||||
__('Expired') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-full bg-amber-500 shadow-lg shadow-amber-500/30"></span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-[0.15em]">{{ __('Warning') }}</span>
|
||||
<span
|
||||
class="w-3 h-3 rounded-full bg-amber-500 shadow-lg shadow-amber-500/30"></span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-[0.15em]">{{
|
||||
__('Warning') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-full bg-emerald-500 shadow-lg shadow-emerald-500/30"></span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-[0.15em]">{{ __('Normal') }}</span>
|
||||
<span
|
||||
class="w-3 h-3 rounded-full bg-emerald-500 shadow-lg shadow-emerald-500/30"></span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-[0.15em]">{{
|
||||
__('Normal') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slots Grid (唯讀) -->
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-5" x-show="!inventoryLoading">
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-5"
|
||||
x-show="!inventoryLoading">
|
||||
<template x-for="slot in inventorySlots" :key="slot.id">
|
||||
<div :class="getSlotColorClass(slot)"
|
||||
class="min-h-[260px] rounded-[2rem] p-5 flex flex-col items-center justify-center border-2 transition-all duration-300 relative">
|
||||
class="min-h-[260px] rounded-[2rem] p-5 flex flex-col items-center justify-center border-2 transition-all duration-300 relative">
|
||||
|
||||
<!-- Slot Header -->
|
||||
<div class="absolute top-3.5 left-4 right-4 flex justify-between items-center z-10">
|
||||
<div class="px-2.5 py-1 rounded-xl bg-slate-900/10 dark:bg-white/10 backdrop-blur-md border border-slate-900/5 dark:border-white/10 flex-shrink-0">
|
||||
<span class="text-xs font-black uppercase tracking-tighter text-slate-800 dark:text-white" x-text="slot.slot_no"></span>
|
||||
<div
|
||||
class="absolute top-3.5 left-4 right-4 flex justify-between items-center z-10">
|
||||
<div
|
||||
class="px-2.5 py-1 rounded-xl bg-slate-900/10 dark:bg-white/10 backdrop-blur-md border border-slate-900/5 dark:border-white/10 flex-shrink-0">
|
||||
<span
|
||||
class="text-xs font-black uppercase tracking-tighter text-slate-800 dark:text-white"
|
||||
x-text="slot.slot_no"></span>
|
||||
</div>
|
||||
<template x-if="slot.stock <= 2">
|
||||
<div class="px-2 py-1 rounded-xl bg-rose-500 text-white text-[9px] font-black uppercase tracking-widest shadow-lg shadow-rose-500/30 animate-pulse whitespace-nowrap select-none">
|
||||
<div
|
||||
class="px-2 py-1 rounded-xl bg-rose-500 text-white text-[9px] font-black uppercase tracking-widest shadow-lg shadow-rose-500/30 animate-pulse whitespace-nowrap select-none">
|
||||
{{ __('Low') }}
|
||||
</div>
|
||||
</template>
|
||||
@@ -743,14 +772,19 @@
|
||||
|
||||
<!-- Product Image -->
|
||||
<div class="relative w-16 h-16 mb-3 mt-2">
|
||||
<div class="absolute inset-0 rounded-2xl bg-white/20 dark:bg-slate-900/40 backdrop-blur-xl border border-white/30 dark:border-white/5 shadow-inner overflow-hidden">
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl bg-white/20 dark:bg-slate-900/40 backdrop-blur-xl border border-white/30 dark:border-white/5 shadow-inner overflow-hidden">
|
||||
<template x-if="slot.product && slot.product.image_url">
|
||||
<img :src="slot.product.image_url" class="w-full h-full object-cover">
|
||||
<img :src="slot.product.image_url"
|
||||
class="w-full h-full object-cover">
|
||||
</template>
|
||||
<template x-if="!slot.product || !slot.product.image_url">
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<svg class="w-7 h-7 opacity-20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
||||
<svg class="w-7 h-7 opacity-20" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2.5"
|
||||
d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
@@ -760,22 +794,29 @@
|
||||
<!-- Slot Info -->
|
||||
<div class="text-center w-full space-y-2">
|
||||
<template x-if="slot.product">
|
||||
<div class="text-sm font-black truncate w-full opacity-90 tracking-tight" x-text="slot.product.name"></div>
|
||||
<div class="text-sm font-black truncate w-full opacity-90 tracking-tight"
|
||||
x-text="slot.product.name"></div>
|
||||
</template>
|
||||
<template x-if="!slot.product">
|
||||
<div class="text-sm font-bold text-slate-300 dark:text-slate-600 tracking-tight">{{ __('Empty') }}</div>
|
||||
<div
|
||||
class="text-sm font-bold text-slate-300 dark:text-slate-600 tracking-tight">
|
||||
{{ __('Empty') }}</div>
|
||||
</template>
|
||||
|
||||
<div class="space-y-2">
|
||||
<!-- Stock Level -->
|
||||
<div class="flex items-baseline justify-center gap-1">
|
||||
<span class="text-xl font-black tracking-tighter leading-none" x-text="slot.stock"></span>
|
||||
<span class="text-xl font-black tracking-tighter leading-none"
|
||||
x-text="slot.stock"></span>
|
||||
<span class="text-xs font-black opacity-30">/</span>
|
||||
<span class="text-sm font-bold opacity-50" x-text="slot.max_stock || 10"></span>
|
||||
<span class="text-sm font-bold opacity-50"
|
||||
x-text="slot.max_stock || 10"></span>
|
||||
</div>
|
||||
|
||||
<!-- Expiry Date -->
|
||||
<div class="text-sm font-black tracking-tight leading-none opacity-80" x-text="slot.expiry_date ? slot.expiry_date.replace(/-/g, '/') : '----/--/--'"></div>
|
||||
<div class="text-sm font-black tracking-tight leading-none opacity-80"
|
||||
x-text="slot.expiry_date ? slot.expiry_date.replace(/-/g, '/') : '----/--/--'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -788,10 +829,10 @@
|
||||
<div class="flex flex-col items-center">
|
||||
<div
|
||||
class="p-4 rounded-full bg-slate-50 dark:bg-slate-800/50 mb-4 border border-slate-100 dark:border-slate-800/50">
|
||||
<svg class="w-8 h-8 text-slate-300 dark:text-slate-600"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="1.5">
|
||||
<path d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
||||
<svg class="w-8 h-8 text-slate-300 dark:text-slate-600" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path
|
||||
d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
|
||||
Reference in New Issue
Block a user