[STYLE] 優化儀表板機台列表互動體驗與庫存導航邏輯

1. 移除儀表板機台狀態表格中不直觀的「操作」(眼睛圖標) 欄位,簡化介面。
2. 將「運行狀態」與「下位機狀態」文字區域改為可點擊連結,導向至機台列表並自動帶入序號篩選。
3. 將「目前庫存」進度條改為可點擊連結,並優化跳轉邏輯改用 machine_id 參數。
4. 優化行動版介面,移除冗餘的「Inventory Overview」按鈕,改為直接點擊庫存進度條即可跳轉。
This commit is contained in:
sky121113 2026-05-14 16:51:21 +08:00
parent 3d1ddd5b91
commit a684628dad

View File

@ -197,7 +197,6 @@
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Today Cumulative Sales') }}</th>
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Current Stock') }}</th>
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Last Signal') }}</th>
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/50">
@ -217,98 +216,94 @@
</div>
</td>
<td class="px-6 py-6 text-center">
@php $cStatus = $machine->calculated_status; @endphp
<div class="flex flex-col items-center gap-0.5">
@if($cStatus === 'online')
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20 tooltip"
title="{{ $machine->is_unstable ? __('Connection is unstable (disconnected :count times in the last hour)', ['count' => $machine->recent_disconnection_count]) : __('Machine connection is normal') }}">
<div class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
<a href="{{ route('admin.machines.index', ['search' => $machine->serial_no]) }}" class="block hover:scale-105 transition-transform">
@php $cStatus = $machine->calculated_status; @endphp
<div class="flex flex-col items-center gap-0.5">
@if($cStatus === 'online')
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20 tooltip"
title="{{ $machine->is_unstable ? __('Connection is unstable (disconnected :count times in the last hour)', ['count' => $machine->recent_disconnection_count]) : __('Machine connection is normal') }}">
<div class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
</div>
<span class="text-[10px] font-black text-emerald-600 dark:text-emerald-400 tracking-widest uppercase">{{ __('Online') }}</span>
@if($machine->is_unstable)
<div class="ml-1 text-amber-500 animate-pulse">
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
<path d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
@endif
</div>
<span class="text-[10px] font-black text-emerald-600 dark:text-emerald-400 tracking-widest uppercase">{{ __('Online') }}</span>
@if($machine->is_unstable)
<div class="ml-1 text-amber-500 animate-pulse">
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
<path d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
@elseif($cStatus === 'warning')
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-amber-500/10 border border-amber-500/20">
<div class="h-2 w-2 rounded-full bg-amber-500 animate-pulse"></div>
<span class="text-[10px] font-black text-amber-600 dark:text-amber-400 tracking-widest uppercase">{{ __('Warning') }}</span>
</div>
@elseif($cStatus === 'error')
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-rose-500/10 border border-rose-500/20">
<div class="h-2 w-2 rounded-full bg-rose-500 animate-pulse"></div>
<span class="text-[10px] font-black text-rose-600 dark:text-rose-400 tracking-widest uppercase">{{ __('Error') }}</span>
</div>
@else
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-slate-500/10 border border-slate-500/20">
<div class="h-2 w-2 rounded-full bg-slate-400"></div>
<span class="text-[10px] font-black text-slate-600 dark:text-slate-400 tracking-widest uppercase">{{ __('Offline') }}</span>
</div>
@endif
@if($machine->latest_status_log_time && in_array($cStatus, ['error', 'warning']))
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_status_log_time->diffForHumans() }}</span>
@endif
</div>
@elseif($cStatus === 'warning')
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-amber-500/10 border border-amber-500/20">
<div class="h-2 w-2 rounded-full bg-amber-500 animate-pulse"></div>
<span class="text-[10px] font-black text-amber-600 dark:text-amber-400 tracking-widest uppercase">{{ __('Warning') }}</span>
</div>
@elseif($cStatus === 'error')
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-rose-500/10 border border-rose-500/20">
<div class="h-2 w-2 rounded-full bg-rose-500 animate-pulse"></div>
<span class="text-[10px] font-black text-rose-600 dark:text-rose-400 tracking-widest uppercase">{{ __('Error') }}</span>
</div>
@else
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-slate-500/10 border border-slate-500/20">
<div class="h-2 w-2 rounded-full bg-slate-400"></div>
<span class="text-[10px] font-black text-slate-600 dark:text-slate-400 tracking-widest uppercase">{{ __('Offline') }}</span>
</div>
@endif
@if($machine->latest_status_log_time && in_array($cStatus, ['error', 'warning']))
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_status_log_time->diffForHumans() }}</span>
@endif
</div>
</a>
</td>
<td class="px-6 py-6 text-center">
@php $hStatus = $machine->hardware_status; @endphp
<div class="flex flex-col items-center gap-0.5">
<div class="flex items-center justify-center gap-1.5">
<span class="w-1.5 h-1.5 rounded-full {{
$hStatus === 'error' ? 'bg-rose-500 animate-pulse' :
($hStatus === 'warning' ? 'bg-amber-500 animate-pulse' : 'bg-emerald-500/50')
}}"></span>
<span class="text-[10px] font-black {{
$hStatus === 'error' ? 'text-rose-500' :
($hStatus === 'warning' ? 'text-amber-500' : 'text-slate-400')
}} uppercase tracking-widest">
{{ $hStatus === 'error' ? __('Error') : ($hStatus === 'warning' ? __('Warning') : __('Normal')) }}
</span>
<a href="{{ route('admin.machines.index', ['search' => $machine->serial_no]) }}" class="block hover:scale-105 transition-transform">
@php $hStatus = $machine->hardware_status; @endphp
<div class="flex flex-col items-center gap-0.5">
<div class="flex items-center justify-center gap-1.5">
<span class="w-1.5 h-1.5 rounded-full {{
$hStatus === 'error' ? 'bg-rose-500 animate-pulse' :
($hStatus === 'warning' ? 'bg-amber-500 animate-pulse' : 'bg-emerald-500/50')
}}"></span>
<span class="text-[10px] font-black {{
$hStatus === 'error' ? 'text-rose-500' :
($hStatus === 'warning' ? 'text-amber-500' : 'text-slate-400')
}} uppercase tracking-widest">
{{ $hStatus === 'error' ? __('Error') : ($hStatus === 'warning' ? __('Warning') : __('Normal')) }}
</span>
</div>
@if($machine->latest_hardware_log_time && $hStatus !== 'normal')
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_hardware_log_time->diffForHumans() }}</span>
@endif
</div>
@if($machine->latest_hardware_log_time && $hStatus !== 'normal')
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_hardware_log_time->diffForHumans() }}</span>
@endif
</div>
</a>
</td>
<td class="px-6 py-6 text-center">
<span class="text-base font-extrabold text-slate-900 dark:text-slate-100">$ {{ number_format($machine->today_sales_sum ?? 0, 0) }}</span>
</td>
<td class="px-6 py-6">
@php
$stockSum = (float)($machine->slots_sum_stock ?? 0);
$maxStockSum = (float)($machine->slots_sum_max_stock ?? 0);
$stockPercentage = $maxStockSum > 0 ? round(($stockSum / $maxStockSum) * 100, 1) : 0;
$barColor = $stockPercentage < 20 ? 'bg-rose-500' : ($stockPercentage < 50 ? 'bg-amber-500' : 'bg-emerald-500');
$textColor = str_replace('bg-', 'text-', $barColor);
@endphp
<div class="flex flex-col items-center gap-y-2.5">
<div class="w-32 h-2 bg-slate-100 dark:bg-white/10 border border-slate-200/60 dark:border-slate-700/30 rounded-full overflow-hidden shadow-inner">
<div class="h-full {{ $barColor }} rounded-full" style="width: {{ $stockPercentage }}%"></div>
<a href="{{ route('admin.warehouses.machine-inventory', ['machine_id' => $machine->id]) }}" class="block group/stock hover:scale-105 transition-transform">
@php
$stockSum = (float)($machine->slots_sum_stock ?? 0);
$maxStockSum = (float)($machine->slots_sum_max_stock ?? 0);
$stockPercentage = $maxStockSum > 0 ? round(($stockSum / $maxStockSum) * 100, 1) : 0;
$barColor = $stockPercentage < 20 ? 'bg-rose-500' : ($stockPercentage < 50 ? 'bg-amber-500' : 'bg-emerald-500');
$textColor = str_replace('bg-', 'text-', $barColor);
@endphp
<div class="flex flex-col items-center gap-y-2.5">
<div class="w-32 h-2 bg-slate-100 dark:bg-white/10 border border-slate-200/60 dark:border-slate-700/30 rounded-full overflow-hidden shadow-inner group-hover/stock:ring-2 group-hover/stock:ring-cyan-500/30 transition-all">
<div class="h-full {{ $barColor }} rounded-full" style="width: {{ $stockPercentage }}%"></div>
</div>
<span class="text-sm font-black {{ $textColor }} uppercase tracking-[0.2em] group-hover/stock:text-cyan-500 transition-colors">{{ $stockPercentage }}%</span>
</div>
<span class="text-sm font-black {{ $textColor }} uppercase tracking-[0.2em]">{{ $stockPercentage }}%</span>
</div>
</a>
</td>
<td class="px-6 py-6 text-center">
<div class="text-xs font-black text-slate-400 dark:text-slate-400/80 uppercase tracking-widest leading-none">
{{ $machine->last_heartbeat_at ? $machine->last_heartbeat_at->format('Y-m-d H:i:s') : '---' }}
</div>
</td>
<td class="px-6 py-6 text-right">
<a href="{{ route('admin.warehouses.machine-inventory', ['machine_id' => $machine->id]) }}"
class="p-2.5 inline-flex items-center justify-center rounded-lg 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 shadow-sm"
title="{{ __('View Inventory Overview') }}">
<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="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" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0z" />
</svg>
</a>
</td>
</tr>
@empty
<tr>
@ -376,38 +371,42 @@
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Status') }}</p>
<div class="flex flex-col">
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 capitalize">
@if($cStatus === 'online')
<span class="text-emerald-500">{{ __('Online') }}</span>
@elseif($cStatus === 'warning')
<span class="text-amber-500">{{ __('Warning') }}</span>
@elseif($cStatus === 'error')
<span class="text-rose-500">{{ __('Error') }}</span>
@else
<span class="text-slate-400">{{ __('Offline') }}</span>
<a href="{{ route('admin.machines.index', ['search' => $machine->serial_no]) }}" class="block hover:scale-105 transition-transform origin-left">
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 capitalize">
@if($cStatus === 'online')
<span class="text-emerald-500">{{ __('Online') }}</span>
@elseif($cStatus === 'warning')
<span class="text-amber-500">{{ __('Warning') }}</span>
@elseif($cStatus === 'error')
<span class="text-rose-500">{{ __('Error') }}</span>
@else
<span class="text-slate-400">{{ __('Offline') }}</span>
@endif
</p>
@if($machine->latest_status_log_time && in_array($cStatus, ['error', 'warning']))
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_status_log_time->diffForHumans() }}</span>
@endif
</p>
@if($machine->latest_status_log_time && in_array($cStatus, ['error', 'warning']))
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_status_log_time->diffForHumans() }}</span>
@endif
</a>
</div>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Sub-machine Status') }}</p>
@php $hStatus = $machine->hardware_status; @endphp
<div class="flex flex-col">
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 capitalize">
@if($hStatus === 'error')
<span class="text-rose-500">{{ __('Error') }}</span>
@elseif($hStatus === 'warning')
<span class="text-amber-500">{{ __('Warning') }}</span>
@else
<span class="text-emerald-500">{{ __('Normal') }}</span>
<a href="{{ route('admin.machines.index', ['search' => $machine->serial_no]) }}" class="block hover:scale-105 transition-transform origin-left">
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 capitalize">
@if($hStatus === 'error')
<span class="text-rose-500">{{ __('Error') }}</span>
@elseif($hStatus === 'warning')
<span class="text-amber-500">{{ __('Warning') }}</span>
@else
<span class="text-emerald-500">{{ __('Normal') }}</span>
@endif
</p>
@if($machine->latest_hardware_log_time && $hStatus !== 'normal')
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_hardware_log_time->diffForHumans() }}</span>
@endif
</p>
@if($machine->latest_hardware_log_time && $hStatus !== 'normal')
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_hardware_log_time->diffForHumans() }}</span>
@endif
</a>
</div>
</div>
<div class="col-span-2">
@ -419,31 +418,23 @@
</div>
<!-- Stock Bar Section (Essential for Dashboard) -->
<div class="mb-6">
@php
$stockSum = (float)($machine->slots_sum_stock ?? 0);
$maxStockSum = (float)($machine->slots_sum_max_stock ?? 0);
$stockPercentage = $maxStockSum > 0 ? round(($stockSum / $maxStockSum) * 100, 1) : 0;
$barColor = $stockPercentage < 20 ? 'bg-rose-500' : ($stockPercentage < 50 ? 'bg-amber-500' : 'bg-emerald-500');
@endphp
<div class="flex justify-between items-end mb-2">
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Current Stock') }}</p>
<span class="text-xs font-black {{ str_replace('bg-', 'text-', $barColor) }}">{{ $stockPercentage }}%</span>
</div>
<div class="w-full h-2 bg-slate-100 dark:bg-white/10 rounded-full overflow-hidden shadow-inner">
<div class="h-full {{ $barColor }} transition-all duration-500" style="width: {{ $stockPercentage }}%"></div>
</div>
<div class="mb-2">
<a href="{{ route('admin.warehouses.machine-inventory', ['machine_id' => $machine->id]) }}" class="block group/stock-mobile">
@php
$stockSum = (float)($machine->slots_sum_stock ?? 0);
$maxStockSum = (float)($machine->slots_sum_max_stock ?? 0);
$stockPercentage = $maxStockSum > 0 ? round(($stockSum / $maxStockSum) * 100, 1) : 0;
$barColor = $stockPercentage < 20 ? 'bg-rose-500' : ($stockPercentage < 50 ? 'bg-amber-500' : 'bg-emerald-500');
@endphp
<div class="flex justify-between items-end mb-2">
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest group-hover/stock-mobile:text-cyan-500 transition-colors">{{ __('Current Stock') }}</p>
<span class="text-xs font-black {{ str_replace('bg-', 'text-', $barColor) }} group-hover/stock-mobile:scale-110 transition-transform">{{ $stockPercentage }}%</span>
</div>
<div class="w-full h-2 bg-slate-100 dark:bg-white/10 rounded-full overflow-hidden shadow-inner group-hover/stock-mobile:ring-2 group-hover/stock-mobile:ring-cyan-500/30 transition-all">
<div class="h-full {{ $barColor }} transition-all duration-500" style="width: {{ $stockPercentage }}%"></div>
</div>
</a>
</div>
<!-- Action Button -->
<a href="{{ route('admin.warehouses.machine-inventory', ['machine_id' => $machine->id]) }}"
class="flex items-center justify-center gap-2 w-full py-3.5 rounded-2xl 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 group/btn">
<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="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" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0z" />
</svg>
{{ __('Inventory Overview') }}
</a>
</div>
@empty
<div class="col-span-full py-20 text-center text-slate-400 font-bold border-2 border-dashed border-slate-100 dark:border-slate-800 rounded-3xl">